mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2025-11-05 11:56:37 +00:00
Merge branch 'master' into satochip_debug
This commit is contained in:
commit
cbcb40c973
68 changed files with 1794 additions and 71 deletions
|
|
@ -7,7 +7,7 @@ plugins {
|
||||||
id 'org.beryx.jlink' version '2.26.0'
|
id 'org.beryx.jlink' version '2.26.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
def sparrowVersion = '1.7.8'
|
def sparrowVersion = '1.7.10'
|
||||||
def os = org.gradle.internal.os.OperatingSystem.current()
|
def os = org.gradle.internal.os.OperatingSystem.current()
|
||||||
def osName = os.getFamilyName()
|
def osName = os.getFamilyName()
|
||||||
if(os.macOsX) {
|
if(os.macOsX) {
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ sudo apt install -y rpm fakeroot binutils
|
||||||
First, assign a temporary variable in your shell for the specific release you want to build. For the current one specify:
|
First, assign a temporary variable in your shell for the specific release you want to build. For the current one specify:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
GIT_TAG="1.7.7"
|
GIT_TAG="1.7.9"
|
||||||
```
|
```
|
||||||
|
|
||||||
The project can then be initially cloned as follows:
|
The project can then be initially cloned as follows:
|
||||||
|
|
|
||||||
2
drongo
2
drongo
|
|
@ -1 +1 @@
|
||||||
Subproject commit 38b04b8e0b802f6cd43b4e88730d4d3ed31227fc
|
Subproject commit 2b7b650faeeeda7fc25ab0962a6132e6531ced4c
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.7.8</string>
|
<string>1.7.10</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
|
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,9 @@ public class AppController implements Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
private MenuItem lockAllWallets;
|
private MenuItem lockAllWallets;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private MenuItem showWalletSummary;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private MenuItem searchWallet;
|
private MenuItem searchWallet;
|
||||||
|
|
||||||
|
|
@ -210,6 +213,8 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
private Timeline statusTimeline;
|
private Timeline statusTimeline;
|
||||||
|
|
||||||
|
private SendToManyDialog sendToManyDialog;
|
||||||
|
|
||||||
private Tab previouslySelectedTab;
|
private Tab previouslySelectedTab;
|
||||||
|
|
||||||
private boolean subTabsVisible;
|
private boolean subTabsVisible;
|
||||||
|
|
@ -218,6 +223,8 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
private final Set<Wallet> emptyLoadingWallets = new LinkedHashSet<>();
|
private final Set<Wallet> emptyLoadingWallets = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
private final Map<File, File> renamedWallets = new HashMap<>();
|
||||||
|
|
||||||
private final ChangeListener<Boolean> serverToggleOnlineListener = (observable, oldValue, newValue) -> {
|
private final ChangeListener<Boolean> serverToggleOnlineListener = (observable, oldValue, newValue) -> {
|
||||||
Platform.runLater(() -> setServerToggleTooltip(getCurrentBlockHeight()));
|
Platform.runLater(() -> setServerToggleTooltip(getCurrentBlockHeight()));
|
||||||
};
|
};
|
||||||
|
|
@ -376,6 +383,7 @@ public class AppController implements Initializable {
|
||||||
deleteWallet.disableProperty().bind(exportWallet.disableProperty());
|
deleteWallet.disableProperty().bind(exportWallet.disableProperty());
|
||||||
closeTab.setDisable(true);
|
closeTab.setDisable(true);
|
||||||
lockWallet.setDisable(true);
|
lockWallet.setDisable(true);
|
||||||
|
showWalletSummary.disableProperty().bind(exportWallet.disableProperty());
|
||||||
searchWallet.disableProperty().bind(exportWallet.disableProperty());
|
searchWallet.disableProperty().bind(exportWallet.disableProperty());
|
||||||
refreshWallet.disableProperty().bind(Bindings.or(exportWallet.disableProperty(), Bindings.or(serverToggle.disableProperty(), AppServices.onlineProperty().not())));
|
refreshWallet.disableProperty().bind(Bindings.or(exportWallet.disableProperty(), Bindings.or(serverToggle.disableProperty(), AppServices.onlineProperty().not())));
|
||||||
sendToMany.disableProperty().bind(exportWallet.disableProperty());
|
sendToMany.disableProperty().bind(exportWallet.disableProperty());
|
||||||
|
|
@ -467,7 +475,17 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void submitBugReport(ActionEvent event) {
|
public void submitBugReport(ActionEvent event) {
|
||||||
AppServices.get().getApplication().getHostServices().showDocument("https://sparrowwallet.com/submitbugreport");
|
ButtonType supportType = new ButtonType("Get Support", ButtonBar.ButtonData.LEFT);
|
||||||
|
ButtonType bugType = new ButtonType("Submit Bug Report", ButtonBar.ButtonData.YES);
|
||||||
|
Optional<ButtonType> optResponse = showWarningDialog("Submit Bug Report", "Please note that this facility is for bug reports and feature requests only. There is a community of Sparrow users who can assist with support requests.", supportType, bugType);
|
||||||
|
|
||||||
|
if(optResponse.isPresent()) {
|
||||||
|
if(optResponse.get() == bugType) {
|
||||||
|
AppServices.get().getApplication().getHostServices().showDocument("https://sparrowwallet.com/submitbugreport");
|
||||||
|
} else {
|
||||||
|
openSupport(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showAbout(ActionEvent event) {
|
public void showAbout(ActionEvent event) {
|
||||||
|
|
@ -839,6 +857,10 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void renameWallet(ActionEvent event) {
|
||||||
|
renameWallet(getSelectedWalletForm());
|
||||||
|
}
|
||||||
|
|
||||||
public void deleteWallet(ActionEvent event) {
|
public void deleteWallet(ActionEvent event) {
|
||||||
deleteWallet(getSelectedWalletForm());
|
deleteWallet(getSelectedWalletForm());
|
||||||
}
|
}
|
||||||
|
|
@ -1286,6 +1308,13 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendToMany(ActionEvent event) {
|
public void sendToMany(ActionEvent event) {
|
||||||
|
if(sendToManyDialog != null) {
|
||||||
|
Stage stage = (Stage)sendToManyDialog.getDialogPane().getScene().getWindow();
|
||||||
|
stage.setAlwaysOnTop(true);
|
||||||
|
stage.setAlwaysOnTop(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
WalletForm selectedWalletForm = getSelectedWalletForm();
|
WalletForm selectedWalletForm = getSelectedWalletForm();
|
||||||
if(selectedWalletForm != null) {
|
if(selectedWalletForm != null) {
|
||||||
Wallet wallet = selectedWalletForm.getWallet();
|
Wallet wallet = selectedWalletForm.getWallet();
|
||||||
|
|
@ -1294,8 +1323,10 @@ public class AppController implements Initializable {
|
||||||
bitcoinUnit = wallet.getAutoUnit();
|
bitcoinUnit = wallet.getAutoUnit();
|
||||||
}
|
}
|
||||||
|
|
||||||
SendToManyDialog sendToManyDialog = new SendToManyDialog(bitcoinUnit);
|
sendToManyDialog = new SendToManyDialog(bitcoinUnit);
|
||||||
|
sendToManyDialog.initModality(Modality.NONE);
|
||||||
Optional<List<Payment>> optPayments = sendToManyDialog.showAndWait();
|
Optional<List<Payment>> optPayments = sendToManyDialog.showAndWait();
|
||||||
|
sendToManyDialog = null;
|
||||||
optPayments.ifPresent(payments -> {
|
optPayments.ifPresent(payments -> {
|
||||||
if(!payments.isEmpty()) {
|
if(!payments.isEmpty()) {
|
||||||
EventManager.get().post(new SendActionEvent(wallet, new ArrayList<>(wallet.getSpendableUtxos().keySet())));
|
EventManager.get().post(new SendActionEvent(wallet, new ArrayList<>(wallet.getSpendableUtxos().keySet())));
|
||||||
|
|
@ -1442,6 +1473,21 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void showWalletSummary(ActionEvent event) {
|
||||||
|
Tab selectedTab = tabs.getSelectionModel().getSelectedItem();
|
||||||
|
if(selectedTab != null) {
|
||||||
|
TabData tabData = (TabData) selectedTab.getUserData();
|
||||||
|
if(tabData instanceof WalletTabData) {
|
||||||
|
TabPane subTabs = (TabPane) selectedTab.getContent();
|
||||||
|
List<WalletForm> walletForms = subTabs.getTabs().stream().map(subTab -> ((WalletTabData)subTab.getUserData()).getWalletForm()).collect(Collectors.toList());
|
||||||
|
if(!walletForms.isEmpty()) {
|
||||||
|
WalletSummaryDialog walletSummaryDialog = new WalletSummaryDialog(walletForms);
|
||||||
|
walletSummaryDialog.showAndWait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void refreshWallet(ActionEvent event) {
|
public void refreshWallet(ActionEvent event) {
|
||||||
WalletForm selectedWalletForm = getSelectedWalletForm();
|
WalletForm selectedWalletForm = getSelectedWalletForm();
|
||||||
if(selectedWalletForm != null) {
|
if(selectedWalletForm != null) {
|
||||||
|
|
@ -1527,6 +1573,11 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
tabs.getTabs().add(tab);
|
tabs.getTabs().add(tab);
|
||||||
tabs.getSelectionModel().select(tab);
|
tabs.getSelectionModel().select(tab);
|
||||||
|
|
||||||
|
File oldWalletFile = renamedWallets.remove(storage.getWalletFile());
|
||||||
|
if(oldWalletFile != null) {
|
||||||
|
deleteStorage(new Storage(oldWalletFile), false);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for(Tab walletTab : tabs.getTabs()) {
|
for(Tab walletTab : tabs.getTabs()) {
|
||||||
TabData tabData = (TabData)walletTab.getUserData();
|
TabData tabData = (TabData)walletTab.getUserData();
|
||||||
|
|
@ -1603,6 +1654,9 @@ public class AppController implements Initializable {
|
||||||
subTabLabel.setGraphic(getSubTabGlyph(wallet));
|
subTabLabel.setGraphic(getSubTabGlyph(wallet));
|
||||||
subTabLabel.setContentDisplay(ContentDisplay.TOP);
|
subTabLabel.setContentDisplay(ContentDisplay.TOP);
|
||||||
subTabLabel.setAlignment(Pos.TOP_CENTER);
|
subTabLabel.setAlignment(Pos.TOP_CENTER);
|
||||||
|
if(isSubTabLabelTruncated(subTabLabel, label)) {
|
||||||
|
subTabLabel.setTooltip(new Tooltip(label));
|
||||||
|
}
|
||||||
subTab.setGraphic(subTabLabel);
|
subTab.setGraphic(subTabLabel);
|
||||||
FXMLLoader walletLoader = new FXMLLoader(getClass().getResource("wallet/wallet.fxml"));
|
FXMLLoader walletLoader = new FXMLLoader(getClass().getResource("wallet/wallet.fxml"));
|
||||||
subTab.setContent(walletLoader.load());
|
subTab.setContent(walletLoader.load());
|
||||||
|
|
@ -1949,6 +2003,30 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void renameWallet(WalletForm selectedWalletForm) {
|
||||||
|
WalletNameDialog walletNameDialog = new WalletNameDialog(selectedWalletForm.getMasterWallet().getName(), false, null, true);
|
||||||
|
Optional<WalletNameDialog.NameAndBirthDate> optName = walletNameDialog.showAndWait();
|
||||||
|
if(optName.isPresent()) {
|
||||||
|
File walletFile = Storage.getWalletFile(optName.get().getName() + "." + PersistenceType.DB.getExtension());
|
||||||
|
if(walletFile.exists()) {
|
||||||
|
showErrorDialog("Error renaming wallet", "Wallet file " + walletFile.getAbsolutePath() + " already exists.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage.CopyWalletService copyWalletService = new Storage.CopyWalletService(selectedWalletForm.getWallet(), walletFile);
|
||||||
|
copyWalletService.setOnSucceeded(event -> {
|
||||||
|
renamedWallets.put(walletFile, selectedWalletForm.getStorage().getWalletFile());
|
||||||
|
tabs.getTabs().remove(tabs.getSelectionModel().getSelectedItem());
|
||||||
|
openWalletFile(walletFile, true);
|
||||||
|
});
|
||||||
|
copyWalletService.setOnFailed(event -> {
|
||||||
|
log.error("Error renaming wallet", event.getSource().getException());
|
||||||
|
showErrorDialog("Error renaming wallet", event.getSource().getException().getMessage());
|
||||||
|
});
|
||||||
|
copyWalletService.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void deleteWallet(WalletForm selectedWalletForm) {
|
private void deleteWallet(WalletForm selectedWalletForm) {
|
||||||
Optional<ButtonType> optButtonType = AppServices.showWarningDialog("Delete " + selectedWalletForm.getWallet().getMasterName() + "?", "The wallet file and any backups will be deleted. Are you sure?", ButtonType.NO, ButtonType.YES);
|
Optional<ButtonType> optButtonType = AppServices.showWarningDialog("Delete " + selectedWalletForm.getWallet().getMasterName() + "?", "The wallet file and any backups will be deleted. Are you sure?", ButtonType.NO, ButtonType.YES);
|
||||||
if(optButtonType.isPresent() && optButtonType.get() == ButtonType.YES) {
|
if(optButtonType.isPresent() && optButtonType.get() == ButtonType.YES) {
|
||||||
|
|
@ -1964,7 +2042,7 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tabs.getTabs().remove(tabs.getSelectionModel().getSelectedItem());
|
tabs.getTabs().remove(tabs.getSelectionModel().getSelectedItem());
|
||||||
deleteStorage(storage);
|
deleteStorage(storage, true);
|
||||||
} finally {
|
} finally {
|
||||||
encryptionFullKey.clear();
|
encryptionFullKey.clear();
|
||||||
password.get().clear();
|
password.get().clear();
|
||||||
|
|
@ -1986,15 +2064,15 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tabs.getTabs().remove(tabs.getSelectionModel().getSelectedItem());
|
tabs.getTabs().remove(tabs.getSelectionModel().getSelectedItem());
|
||||||
deleteStorage(storage);
|
deleteStorage(storage, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteStorage(Storage storage) {
|
private void deleteStorage(Storage storage, boolean deleteBackups) {
|
||||||
if(storage.isClosed()) {
|
if(storage.isClosed()) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
Storage.DeleteWalletService deleteWalletService = new Storage.DeleteWalletService(storage);
|
Storage.DeleteWalletService deleteWalletService = new Storage.DeleteWalletService(storage, deleteBackups);
|
||||||
deleteWalletService.setDelay(Duration.seconds(3));
|
deleteWalletService.setDelay(Duration.seconds(3));
|
||||||
deleteWalletService.setPeriod(Duration.hours(1));
|
deleteWalletService.setPeriod(Duration.hours(1));
|
||||||
deleteWalletService.setOnSucceeded(event -> {
|
deleteWalletService.setOnSucceeded(event -> {
|
||||||
|
|
@ -2010,7 +2088,7 @@ public class AppController implements Initializable {
|
||||||
deleteWalletService.start();
|
deleteWalletService.start();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Platform.runLater(() -> deleteStorage(storage));
|
Platform.runLater(() -> deleteStorage(storage, deleteBackups));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2026,6 +2104,11 @@ public class AppController implements Initializable {
|
||||||
if(optLabel.isPresent()) {
|
if(optLabel.isPresent()) {
|
||||||
String label = optLabel.get();
|
String label = optLabel.get();
|
||||||
subTabLabel.setText(label);
|
subTabLabel.setText(label);
|
||||||
|
if(isSubTabLabelTruncated(subTabLabel, label)) {
|
||||||
|
subTabLabel.setTooltip(new Tooltip(label));
|
||||||
|
} else {
|
||||||
|
subTabLabel.setTooltip(null);
|
||||||
|
}
|
||||||
|
|
||||||
Wallet renamedWallet = AppServices.get().getWallet(walletId);
|
Wallet renamedWallet = AppServices.get().getWallet(walletId);
|
||||||
renamedWallet.setLabel(label);
|
renamedWallet.setLabel(label);
|
||||||
|
|
@ -2050,9 +2133,18 @@ public class AppController implements Initializable {
|
||||||
contextMenu.getItems().add(delete);
|
contextMenu.getItems().add(delete);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contextMenu.setOnShowing(event -> {
|
||||||
|
Wallet renameWallet = AppServices.get().getWallet(walletId);
|
||||||
|
rename.setDisable(!renameWallet.isValid());
|
||||||
|
});
|
||||||
|
|
||||||
return contextMenu;
|
return contextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isSubTabLabelTruncated(Label subTabLabel, String label) {
|
||||||
|
return TextUtils.computeTextWidth(subTabLabel.getFont(), label, 0.0D) > (90-6);
|
||||||
|
}
|
||||||
|
|
||||||
private void configureSwitchServer() {
|
private void configureSwitchServer() {
|
||||||
switchServer.getItems().clear();
|
switchServer.getItems().clear();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ public class DefaultInteractionServices implements InteractionServices {
|
||||||
alert.getDialogPane().setPrefHeight(200 + numLines * 20);
|
alert.getDialogPane().setPrefHeight(200 + numLines * 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alert.setResizable(true);
|
||||||
|
|
||||||
moveToActiveWindowScreen(alert);
|
moveToActiveWindowScreen(alert);
|
||||||
return alert.showAndWait();
|
return alert.showAndWait();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import java.util.*;
|
||||||
public class SparrowWallet {
|
public class SparrowWallet {
|
||||||
public static final String APP_ID = "com.sparrowwallet.sparrow";
|
public static final String APP_ID = "com.sparrowwallet.sparrow";
|
||||||
public static final String APP_NAME = "Sparrow";
|
public static final String APP_NAME = "Sparrow";
|
||||||
public static final String APP_VERSION = "1.7.8";
|
public static final String APP_VERSION = "1.7.10";
|
||||||
public static final String APP_VERSION_SUFFIX = "";
|
public static final String APP_VERSION_SUFFIX = "";
|
||||||
public static final String APP_HOME_PROPERTY = "sparrow.home";
|
public static final String APP_HOME_PROPERTY = "sparrow.home";
|
||||||
public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK";
|
public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK";
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.BitcoinUnit;
|
import com.sparrowwallet.drongo.BitcoinUnit;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.CurrencyRate;
|
||||||
import com.sparrowwallet.sparrow.UnitFormat;
|
import com.sparrowwallet.sparrow.UnitFormat;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
|
@ -17,6 +18,7 @@ import javafx.geometry.Pos;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Hyperlink;
|
import javafx.scene.control.Hyperlink;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.TreeTableColumn;
|
||||||
import javafx.scene.control.TreeTableView;
|
import javafx.scene.control.TreeTableView;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
|
|
@ -28,6 +30,7 @@ import java.util.Optional;
|
||||||
public class CoinTreeTable extends TreeTableView<Entry> {
|
public class CoinTreeTable extends TreeTableView<Entry> {
|
||||||
private BitcoinUnit bitcoinUnit;
|
private BitcoinUnit bitcoinUnit;
|
||||||
private UnitFormat unitFormat;
|
private UnitFormat unitFormat;
|
||||||
|
private CurrencyRate currencyRate;
|
||||||
|
|
||||||
public BitcoinUnit getBitcoinUnit() {
|
public BitcoinUnit getBitcoinUnit() {
|
||||||
return bitcoinUnit;
|
return bitcoinUnit;
|
||||||
|
|
@ -64,6 +67,18 @@ public class CoinTreeTable extends TreeTableView<Entry> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CurrencyRate getCurrencyRate() {
|
||||||
|
return currencyRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrencyRate(CurrencyRate currencyRate) {
|
||||||
|
this.currencyRate = currencyRate;
|
||||||
|
|
||||||
|
if(!getChildren().isEmpty()) {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void updateHistoryStatus(WalletHistoryStatusEvent event) {
|
public void updateHistoryStatus(WalletHistoryStatusEvent event) {
|
||||||
if(getRoot() != null) {
|
if(getRoot() != null) {
|
||||||
Entry entry = getRoot().getValue();
|
Entry entry = getRoot().getValue();
|
||||||
|
|
@ -119,4 +134,12 @@ public class CoinTreeTable extends TreeTableView<Entry> {
|
||||||
stackPane.setAlignment(Pos.CENTER);
|
stackPane.setAlignment(Pos.CENTER);
|
||||||
return stackPane;
|
return stackPane;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSortColumn(int columnIndex, TreeTableColumn.SortType sortType) {
|
||||||
|
if(columnIndex >= 0 && columnIndex < getColumns().size() && getSortOrder().isEmpty() && !getRoot().getChildren().isEmpty()) {
|
||||||
|
TreeTableColumn<Entry, ?> column = getColumns().get(columnIndex);
|
||||||
|
column.setSortType(sortType == null ? TreeTableColumn.SortType.DESCENDING : sortType);
|
||||||
|
getSortOrder().add(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -301,7 +301,7 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
|
|
||||||
if(importButton instanceof SplitMenuButton importMenuButton) {
|
if(importButton instanceof SplitMenuButton importMenuButton) {
|
||||||
if(wallet.getScriptType() == null) {
|
if(wallet.getScriptType() == null) {
|
||||||
ScriptType[] scriptTypes = new ScriptType[] {ScriptType.P2WPKH, ScriptType.P2SH_P2WPKH, ScriptType.P2PKH};
|
ScriptType[] scriptTypes = new ScriptType[] {ScriptType.P2WPKH, ScriptType.P2SH_P2WPKH, ScriptType.P2PKH, ScriptType.P2TR};
|
||||||
for(ScriptType scriptType : scriptTypes) {
|
for(ScriptType scriptType : scriptTypes) {
|
||||||
MenuItem item = new MenuItem(scriptType.getDescription());
|
MenuItem item = new MenuItem(scriptType.getDescription());
|
||||||
final List<ChildNumber> derivation = scriptType.getDefaultDerivation();
|
final List<ChildNumber> derivation = scriptType.getDefaultDerivation();
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
actionBox.getChildren().add(viewTransactionButton);
|
actionBox.getChildren().add(viewTransactionButton);
|
||||||
|
|
||||||
BlockTransaction blockTransaction = transactionEntry.getBlockTransaction();
|
BlockTransaction blockTransaction = transactionEntry.getBlockTransaction();
|
||||||
if(blockTransaction.getHeight() <= 0 && blockTransaction.getTransaction().isReplaceByFee() &&
|
if(blockTransaction.getHeight() <= 0 && canRBF(blockTransaction) &&
|
||||||
Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
||||||
Button increaseFeeButton = new Button("");
|
Button increaseFeeButton = new Button("");
|
||||||
increaseFeeButton.setGraphic(getIncreaseFeeRBFGlyph());
|
increaseFeeButton.setGraphic(getIncreaseFeeRBFGlyph());
|
||||||
|
|
@ -216,7 +216,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
.map(e -> (HashIndexEntry)e)
|
.map(e -> (HashIndexEntry)e)
|
||||||
.filter(e -> e.getType().equals(HashIndexEntry.Type.INPUT) && e.isSpendable())
|
.filter(e -> e.getType().equals(HashIndexEntry.Type.INPUT) && e.isSpendable())
|
||||||
.map(e -> blockTransaction.getTransaction().getInputs().get((int)e.getHashIndex().getIndex()))
|
.map(e -> blockTransaction.getTransaction().getInputs().get((int)e.getHashIndex().getIndex()))
|
||||||
.filter(TransactionInput::isReplaceByFeeEnabled)
|
.filter(i -> Config.get().isMempoolFullRbf() || i.isReplaceByFeeEnabled())
|
||||||
.map(txInput -> walletTxos.keySet().stream().filter(txo -> txo.getHash().equals(txInput.getOutpoint().getHash()) && txo.getIndex() == txInput.getOutpoint().getIndex()).findFirst().get())
|
.map(txInput -> walletTxos.keySet().stream().filter(txo -> txo.getHash().equals(txInput.getOutpoint().getHash()) && txo.getIndex() == txInput.getOutpoint().getIndex()).findFirst().get())
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
|
@ -240,6 +240,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
.map(e -> e.getBlockTransaction().getTransaction().getOutputs().get((int)e.getHashIndex().getIndex()))
|
.map(e -> e.getBlockTransaction().getTransaction().getOutputs().get((int)e.getHashIndex().getIndex()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
boolean consolidationTransaction = consolidationOutputs.size() == blockTransaction.getTransaction().getOutputs().size() && consolidationOutputs.size() == 1;
|
||||||
long changeTotal = ourOutputs.stream().mapToLong(TransactionOutput::getValue).sum() - consolidationOutputs.stream().mapToLong(TransactionOutput::getValue).sum();
|
long changeTotal = ourOutputs.stream().mapToLong(TransactionOutput::getValue).sum() - consolidationOutputs.stream().mapToLong(TransactionOutput::getValue).sum();
|
||||||
Transaction tx = blockTransaction.getTransaction();
|
Transaction tx = blockTransaction.getTransaction();
|
||||||
double vSize = tx.getVirtualSize();
|
double vSize = tx.getVirtualSize();
|
||||||
|
|
@ -254,7 +255,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
List<OutputGroup> outputGroups = transactionEntry.getWallet().getGroupedUtxos(txoFilters, feeRate, AppServices.getMinimumRelayFeeRate(), Config.get().isGroupByAddress())
|
List<OutputGroup> outputGroups = transactionEntry.getWallet().getGroupedUtxos(txoFilters, feeRate, AppServices.getMinimumRelayFeeRate(), Config.get().isGroupByAddress())
|
||||||
.stream().filter(outputGroup -> outputGroup.getEffectiveValue() >= 0).collect(Collectors.toList());
|
.stream().filter(outputGroup -> outputGroup.getEffectiveValue() >= 0).collect(Collectors.toList());
|
||||||
Collections.shuffle(outputGroups);
|
Collections.shuffle(outputGroups);
|
||||||
while((double)changeTotal / vSize < getMaxFeeRate() && !outputGroups.isEmpty() && !cancelTransaction) {
|
while((double)changeTotal / vSize < getMaxFeeRate() && !outputGroups.isEmpty() && !cancelTransaction && !consolidationTransaction) {
|
||||||
//If there is insufficient change output, include another random output group so the fee can be increased
|
//If there is insufficient change output, include another random output group so the fee can be increased
|
||||||
OutputGroup outputGroup = outputGroups.remove(0);
|
OutputGroup outputGroup = outputGroups.remove(0);
|
||||||
for(BlockTransactionHashIndex utxo : outputGroup.getUtxos()) {
|
for(BlockTransactionHashIndex utxo : outputGroup.getUtxos()) {
|
||||||
|
|
@ -392,6 +393,10 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(transactionEntry.getWallet(), utxos, List.of(payment), null, blockTransaction.getFee(), true, null)));
|
Platform.runLater(() -> EventManager.get().post(new SpendUtxoEvent(transactionEntry.getWallet(), utxos, List.of(payment), null, blockTransaction.getFee(), true, null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean canRBF(BlockTransaction blockTransaction) {
|
||||||
|
return Config.get().isMempoolFullRbf() || blockTransaction.getTransaction().isReplaceByFee();
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean canSignMessage(WalletNode walletNode) {
|
private static boolean canSignMessage(WalletNode walletNode) {
|
||||||
Wallet wallet = walletNode.getWallet();
|
Wallet wallet = walletNode.getWallet();
|
||||||
return wallet.getKeystores().size() == 1 &&
|
return wallet.getKeystores().size() == 1 &&
|
||||||
|
|
@ -469,7 +474,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
tooltip += "\nFee rate: " + String.format("%.2f", feeRate) + " sats/vB";
|
tooltip += "\nFee rate: " + String.format("%.2f", feeRate) + " sats/vB";
|
||||||
}
|
}
|
||||||
|
|
||||||
tooltip += "\nRBF: " + (transactionEntry.getBlockTransaction().getTransaction().isReplaceByFee() ? "Enabled" : "Disabled");
|
tooltip += "\nRBF: " + (canRBF(transactionEntry.getBlockTransaction()) ? "Enabled" : "Disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
return tooltip;
|
return tooltip;
|
||||||
|
|
@ -546,7 +551,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
});
|
});
|
||||||
getItems().add(viewTransaction);
|
getItems().add(viewTransaction);
|
||||||
|
|
||||||
if(blockTransaction.getTransaction().isReplaceByFee() && Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
if(canRBF(blockTransaction) && Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
||||||
MenuItem increaseFee = new MenuItem("Increase Fee (RBF)");
|
MenuItem increaseFee = new MenuItem("Increase Fee (RBF)");
|
||||||
increaseFee.setGraphic(getIncreaseFeeRBFGlyph());
|
increaseFee.setGraphic(getIncreaseFeeRBFGlyph());
|
||||||
increaseFee.setOnAction(AE -> {
|
increaseFee.setOnAction(AE -> {
|
||||||
|
|
@ -557,7 +562,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
getItems().add(increaseFee);
|
getItems().add(increaseFee);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(blockTransaction.getTransaction().isReplaceByFee() && Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
if(canRBF(blockTransaction) && Config.get().isIncludeMempoolOutputs() && transactionEntry.getWallet().allInputsFromWallet(blockTransaction.getHash())) {
|
||||||
MenuItem cancelTx = new MenuItem("Cancel Transaction (RBF)");
|
MenuItem cancelTx = new MenuItem("Cancel Transaction (RBF)");
|
||||||
cancelTx.setGraphic(getCancelTransactionRBFGlyph());
|
cancelTx.setGraphic(getCancelTransactionRBFGlyph());
|
||||||
cancelTx.setOnAction(AE -> {
|
cancelTx.setOnAction(AE -> {
|
||||||
|
|
@ -789,6 +794,8 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
cell.getStyleClass().remove("transaction-row");
|
cell.getStyleClass().remove("transaction-row");
|
||||||
cell.getStyleClass().remove("node-row");
|
cell.getStyleClass().remove("node-row");
|
||||||
cell.getStyleClass().remove("utxo-row");
|
cell.getStyleClass().remove("utxo-row");
|
||||||
|
cell.getStyleClass().remove("unconfirmed-row");
|
||||||
|
cell.getStyleClass().remove("summary-row");
|
||||||
cell.getStyleClass().remove("address-cell");
|
cell.getStyleClass().remove("address-cell");
|
||||||
cell.getStyleClass().remove("hashindex-row");
|
cell.getStyleClass().remove("hashindex-row");
|
||||||
cell.getStyleClass().remove("confirming");
|
cell.getStyleClass().remove("confirming");
|
||||||
|
|
@ -823,6 +830,10 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
||||||
if(hashIndexEntry.isSpent()) {
|
if(hashIndexEntry.isSpent()) {
|
||||||
cell.getStyleClass().add("spent");
|
cell.getStyleClass().add("spent");
|
||||||
}
|
}
|
||||||
|
} else if(entry instanceof WalletSummaryDialog.UnconfirmedEntry) {
|
||||||
|
cell.getStyleClass().add("unconfirmed-row");
|
||||||
|
} else if(entry instanceof WalletSummaryDialog.SummaryEntry) {
|
||||||
|
cell.getStyleClass().add("summary-row");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||||
|
import com.sparrowwallet.sparrow.CurrencyRate;
|
||||||
|
import com.sparrowwallet.sparrow.UnitFormat;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.Entry;
|
||||||
|
import javafx.scene.control.ContextMenu;
|
||||||
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.control.TreeTableCell;
|
||||||
|
import javafx.scene.input.Clipboard;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
import org.controlsfx.tools.Platform;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Currency;
|
||||||
|
|
||||||
|
public class FiatCell extends TreeTableCell<Entry, Number> {
|
||||||
|
private final Tooltip tooltip;
|
||||||
|
private final FiatContextMenu contextMenu;
|
||||||
|
|
||||||
|
public FiatCell() {
|
||||||
|
super();
|
||||||
|
tooltip = new Tooltip();
|
||||||
|
contextMenu = new FiatContextMenu();
|
||||||
|
getStyleClass().add("coin-cell");
|
||||||
|
if(Platform.getCurrent() == Platform.OSX) {
|
||||||
|
getStyleClass().add("number-field");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Number amount, boolean empty) {
|
||||||
|
super.updateItem(amount, empty);
|
||||||
|
|
||||||
|
if(empty || amount == null) {
|
||||||
|
setText(null);
|
||||||
|
setGraphic(null);
|
||||||
|
setTooltip(null);
|
||||||
|
setContextMenu(null);
|
||||||
|
} else {
|
||||||
|
Entry entry = getTreeTableView().getTreeItem(getIndex()).getValue();
|
||||||
|
EntryCell.applyRowStyles(this, entry);
|
||||||
|
|
||||||
|
CoinTreeTable coinTreeTable = (CoinTreeTable) getTreeTableView();
|
||||||
|
UnitFormat format = coinTreeTable.getUnitFormat();
|
||||||
|
CurrencyRate currencyRate = coinTreeTable.getCurrencyRate();
|
||||||
|
|
||||||
|
if(currencyRate != null && currencyRate.isAvailable()) {
|
||||||
|
Currency currency = currencyRate.getCurrency();
|
||||||
|
double btcRate = currencyRate.getBtcRate();
|
||||||
|
|
||||||
|
BigDecimal satsBalance = BigDecimal.valueOf(amount.longValue());
|
||||||
|
BigDecimal btcBalance = satsBalance.divide(BigDecimal.valueOf(Transaction.SATOSHIS_PER_BITCOIN));
|
||||||
|
BigDecimal fiatBalance = btcBalance.multiply(BigDecimal.valueOf(btcRate));
|
||||||
|
|
||||||
|
String label = format.formatCurrencyValue(fiatBalance.doubleValue());
|
||||||
|
tooltip.setText("1 BTC = " + currency.getSymbol() + " " + format.formatCurrencyValue(btcRate));
|
||||||
|
|
||||||
|
setText(label);
|
||||||
|
setGraphic(null);
|
||||||
|
setTooltip(tooltip);
|
||||||
|
setContextMenu(contextMenu);
|
||||||
|
} else {
|
||||||
|
setText(null);
|
||||||
|
setGraphic(null);
|
||||||
|
setTooltip(null);
|
||||||
|
setContextMenu(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FiatContextMenu extends ContextMenu {
|
||||||
|
public FiatContextMenu() {
|
||||||
|
MenuItem copyValue = new MenuItem("Copy Value");
|
||||||
|
copyValue.setOnAction(AE -> {
|
||||||
|
hide();
|
||||||
|
ClipboardContent content = new ClipboardContent();
|
||||||
|
content.putString(getText());
|
||||||
|
Clipboard.getSystemClipboard().setContent(content);
|
||||||
|
});
|
||||||
|
|
||||||
|
MenuItem copyRate = new MenuItem("Copy Rate");
|
||||||
|
copyRate.setOnAction(AE -> {
|
||||||
|
hide();
|
||||||
|
ClipboardContent content = new ClipboardContent();
|
||||||
|
content.putString(getTooltip().getText());
|
||||||
|
Clipboard.getSystemClipboard().setContent(content);
|
||||||
|
});
|
||||||
|
|
||||||
|
getItems().addAll(copyValue, copyRate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ public class FileWalletImportPane extends FileImportPane {
|
||||||
private final WalletImport importer;
|
private final WalletImport importer;
|
||||||
|
|
||||||
public FileWalletImportPane(WalletImport importer) {
|
public FileWalletImportPane(WalletImport importer) {
|
||||||
super(importer, importer.getName(), "Wallet import", importer.getWalletImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isWalletImportScannable(), true);
|
super(importer, importer.getName(), "Wallet import", importer.getWalletImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isWalletImportScannable(), importer.isWalletImportFileFormatAvailable());
|
||||||
this.importer = importer;
|
this.importer = importer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -392,7 +392,7 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!verified && Bip322.isSupported(getAddress().getScriptType())) {
|
if(!verified && Bip322.isSupported(getAddress().getScriptType()) && !signature.getText().trim().isEmpty()) {
|
||||||
try {
|
try {
|
||||||
verified = Bip322.verifyMessageBip322(getAddress().getScriptType(), getAddress(), message.getText().trim(), signature.getText().trim());
|
verified = Bip322.verifyMessageBip322(getAddress().getScriptType(), getAddress(), message.getText().trim(), signature.getText().trim());
|
||||||
if(verified) {
|
if(verified) {
|
||||||
|
|
|
||||||
|
|
@ -502,18 +502,22 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
if(cryptoOutput.getMultiKey() != null) {
|
if(cryptoOutput.getMultiKey() != null) {
|
||||||
MultiKey multiKey = cryptoOutput.getMultiKey();
|
MultiKey multiKey = cryptoOutput.getMultiKey();
|
||||||
Map<ExtendedKey, KeyDerivation> extendedPublicKeys = new LinkedHashMap<>();
|
Map<ExtendedKey, KeyDerivation> extendedPublicKeys = new LinkedHashMap<>();
|
||||||
|
Map<ExtendedKey, String> extendedPublicKeyLabels = new LinkedHashMap<>();
|
||||||
for(CryptoHDKey cryptoHDKey : multiKey.getHdKeys()) {
|
for(CryptoHDKey cryptoHDKey : multiKey.getHdKeys()) {
|
||||||
ExtendedKey extendedKey = getExtendedKey(cryptoHDKey);
|
ExtendedKey extendedKey = getExtendedKey(cryptoHDKey);
|
||||||
KeyDerivation keyDerivation = getKeyDerivation(cryptoHDKey.getOrigin());
|
KeyDerivation keyDerivation = getKeyDerivation(cryptoHDKey.getOrigin());
|
||||||
extendedPublicKeys.put(extendedKey, keyDerivation);
|
extendedPublicKeys.put(extendedKey, keyDerivation);
|
||||||
|
if(cryptoHDKey.getName() != null) {
|
||||||
|
extendedPublicKeyLabels.put(extendedKey, cryptoHDKey.getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new OutputDescriptor(scriptType, multiKey.getThreshold(), extendedPublicKeys);
|
return new OutputDescriptor(scriptType, multiKey.getThreshold(), extendedPublicKeys, new LinkedHashMap<>(), extendedPublicKeyLabels);
|
||||||
} else if(cryptoOutput.getEcKey() != null) {
|
} else if(cryptoOutput.getEcKey() != null) {
|
||||||
throw new IllegalArgumentException("EC keys are currently unsupported");
|
throw new IllegalArgumentException("EC keys are currently unsupported");
|
||||||
} else if(cryptoOutput.getHdKey() != null) {
|
} else if(cryptoOutput.getHdKey() != null) {
|
||||||
ExtendedKey extendedKey = getExtendedKey(cryptoOutput.getHdKey());
|
ExtendedKey extendedKey = getExtendedKey(cryptoOutput.getHdKey());
|
||||||
KeyDerivation keyDerivation = getKeyDerivation(cryptoOutput.getHdKey().getOrigin());
|
KeyDerivation keyDerivation = getKeyDerivation(cryptoOutput.getHdKey().getOrigin());
|
||||||
return new OutputDescriptor(scriptType, extendedKey, keyDerivation);
|
return new OutputDescriptor(scriptType, extendedKey, keyDerivation, cryptoOutput.getHdKey().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalStateException("CryptoOutput did not contain sufficient information");
|
throw new IllegalStateException("CryptoOutput did not contain sufficient information");
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,8 @@ public class SearchWalletDialog extends Dialog<Entry> {
|
||||||
|
|
||||||
setResizable(true);
|
setResizable(true);
|
||||||
|
|
||||||
|
AppServices.moveToActiveWindowScreen(this);
|
||||||
|
|
||||||
Platform.runLater(search::requestFocus);
|
Platform.runLater(search::requestFocus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,7 @@ public class TransactionsTreeTable extends CoinTreeTable {
|
||||||
setPlaceholder(getDefaultPlaceholder(rootEntry.getWallet()));
|
setPlaceholder(getDefaultPlaceholder(rootEntry.getWallet()));
|
||||||
setEditable(true);
|
setEditable(true);
|
||||||
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||||
dateCol.setSortType(TreeTableColumn.SortType.DESCENDING);
|
setSortColumn(0, TreeTableColumn.SortType.DESCENDING);
|
||||||
getSortOrder().add(dateCol);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateAll(WalletTransactionsEntry rootEntry) {
|
public void updateAll(WalletTransactionsEntry rootEntry) {
|
||||||
|
|
@ -61,16 +60,13 @@ public class TransactionsTreeTable extends CoinTreeTable {
|
||||||
setRoot(rootItem);
|
setRoot(rootItem);
|
||||||
rootItem.setExpanded(true);
|
rootItem.setExpanded(true);
|
||||||
|
|
||||||
if(getColumns().size() > 0 && getSortOrder().isEmpty()) {
|
setSortColumn(0, TreeTableColumn.SortType.DESCENDING);
|
||||||
TreeTableColumn<Entry, ?> dateCol = getColumns().get(0);
|
|
||||||
getSortOrder().add(dateCol);
|
|
||||||
dateCol.setSortType(TreeTableColumn.SortType.DESCENDING);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateHistory() {
|
public void updateHistory() {
|
||||||
//Transaction entries should have already been updated using WalletTransactionsEntry.updateHistory, so only a resort required
|
//Transaction entries should have already been updated using WalletTransactionsEntry.updateHistory, so only a resort required
|
||||||
sort();
|
sort();
|
||||||
|
setSortColumn(0, TreeTableColumn.SortType.DESCENDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateLabel(Entry entry) {
|
public void updateLabel(Entry entry) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import javafx.scene.control.TreeTableColumn;
|
||||||
|
|
||||||
|
public class TreeTableColumnConfig {
|
||||||
|
private final int index;
|
||||||
|
private final Integer width;
|
||||||
|
private final TreeTableColumn.SortType sortType;
|
||||||
|
|
||||||
|
public TreeTableColumnConfig(int index, Integer width, TreeTableColumn.SortType sortType) {
|
||||||
|
this.index = index;
|
||||||
|
this.width = width;
|
||||||
|
this.sortType = sortType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeTableColumnConfig(int index, String width, String sortType) {
|
||||||
|
this.index = index;
|
||||||
|
this.width = width.isEmpty() ? null : Integer.valueOf(width, 10);
|
||||||
|
this.sortType = sortType.isEmpty() ? null : TreeTableColumn.SortType.valueOf(sortType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndex() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeTableColumn.SortType getSortType() {
|
||||||
|
return sortType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return index + "-" + (width == null ? "" : width) + "-" + (sortType == null ? "" : sortType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TreeTableColumnConfig fromString(String columnConfig) {
|
||||||
|
String[] parts = columnConfig.split("-", 3);
|
||||||
|
if(parts.length == 3) {
|
||||||
|
return new TreeTableColumnConfig(Integer.parseInt(parts[0]), parts[1], parts[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TreeTableColumnConfig(Integer.parseInt(parts[0]), (Integer)null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.sparrow.wallet.Entry;
|
||||||
|
import javafx.scene.control.TreeTableColumn;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
|
public class TreeTableConfig {
|
||||||
|
private final List<TreeTableColumnConfig> columnConfigs;
|
||||||
|
|
||||||
|
public TreeTableConfig(CoinTreeTable treeTable) {
|
||||||
|
columnConfigs = new ArrayList<>();
|
||||||
|
TreeTableColumn<Entry, ?> sortColumn = treeTable.getSortOrder().isEmpty() ? null : treeTable.getSortOrder().get(0);
|
||||||
|
for(int i = 0; i < treeTable.getColumns().size(); i++) {
|
||||||
|
TreeTableColumn<Entry, ?> column = treeTable.getColumns().get(i);
|
||||||
|
//TODO: Support column widths
|
||||||
|
columnConfigs.add(new TreeTableColumnConfig(i, null, sortColumn == column ? column.getSortType() : null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeTableConfig(List<TreeTableColumnConfig> columnConfigs) {
|
||||||
|
this.columnConfigs = columnConfigs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringJoiner joiner = new StringJoiner("|");
|
||||||
|
columnConfigs.stream().forEach(col -> joiner.add(col.toString()));
|
||||||
|
return joiner.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TreeTableConfig fromString(String tableConfig) {
|
||||||
|
List<TreeTableColumnConfig> columnConfigs = new ArrayList<>();
|
||||||
|
String[] parts = tableConfig.split("\\|");
|
||||||
|
for(String part : parts) {
|
||||||
|
columnConfigs.add(TreeTableColumnConfig.fromString(part));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TreeTableConfig(columnConfigs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -83,8 +83,7 @@ public class UtxosTreeTable extends CoinTreeTable {
|
||||||
setPlaceholder(getDefaultPlaceholder(rootEntry.getWallet()));
|
setPlaceholder(getDefaultPlaceholder(rootEntry.getWallet()));
|
||||||
setEditable(true);
|
setEditable(true);
|
||||||
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||||
amountCol.setSortType(TreeTableColumn.SortType.DESCENDING);
|
setSortColumn(getColumns().size() - 1, TreeTableColumn.SortType.DESCENDING);
|
||||||
getSortOrder().add(amountCol);
|
|
||||||
|
|
||||||
getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||||
}
|
}
|
||||||
|
|
@ -96,17 +95,14 @@ public class UtxosTreeTable extends CoinTreeTable {
|
||||||
setRoot(rootItem);
|
setRoot(rootItem);
|
||||||
rootItem.setExpanded(true);
|
rootItem.setExpanded(true);
|
||||||
|
|
||||||
if(getColumns().size() > 0 && getSortOrder().isEmpty()) {
|
setSortColumn(getColumns().size() - 1, TreeTableColumn.SortType.DESCENDING);
|
||||||
TreeTableColumn<Entry, ?> amountCol = getColumns().get(getColumns().size() - 1);
|
|
||||||
getSortOrder().add(amountCol);
|
|
||||||
amountCol.setSortType(TreeTableColumn.SortType.DESCENDING);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateHistory() {
|
public void updateHistory() {
|
||||||
//Utxo entries should have already been updated, so only a resort required
|
//Utxo entries should have already been updated, so only a resort required
|
||||||
if(!getRoot().getChildren().isEmpty()) {
|
if(!getRoot().getChildren().isEmpty()) {
|
||||||
sort();
|
sort();
|
||||||
|
setSortColumn(getColumns().size() - 1, TreeTableColumn.SortType.DESCENDING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ public class WalletImportDialog extends Dialog<Wallet> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<WalletImport> walletImporters = new ArrayList<>(List.of(new Bip129(), new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new KeystoneMultisig(), new Descriptor(), new SpecterDesktop(), new BlueWalletMultisig(), new Sparrow()));
|
List<WalletImport> walletImporters = new ArrayList<>(List.of(new Bip129(), new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new KeystoneMultisig(), new Descriptor(), new SpecterDesktop(), new BlueWalletMultisig(), new Sparrow(), new JadeMultisig()));
|
||||||
if(!selectedWalletForms.isEmpty()) {
|
if(!selectedWalletForms.isEmpty()) {
|
||||||
walletImporters.add(new WalletLabels(selectedWalletForms));
|
walletImporters.add(new WalletLabels(selectedWalletForms));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,13 @@ public class WalletNameDialog extends Dialog<WalletNameDialog.NameAndBirthDate>
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletNameDialog(String initialName, boolean hasExistingTransactions, Date startDate) {
|
public WalletNameDialog(String initialName, boolean hasExistingTransactions, Date startDate) {
|
||||||
|
this(initialName, hasExistingTransactions, startDate, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WalletNameDialog(String initialName, boolean hasExistingTransactions, Date startDate, boolean rename) {
|
||||||
final DialogPane dialogPane = getDialogPane();
|
final DialogPane dialogPane = getDialogPane();
|
||||||
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
||||||
boolean requestBirthDate = (Config.get().getServerType() == null || Config.get().getServerType() == ServerType.BITCOIN_CORE);
|
boolean requestBirthDate = !rename && (Config.get().getServerType() == null || Config.get().getServerType() == ServerType.BITCOIN_CORE);
|
||||||
|
|
||||||
setTitle("Wallet Name");
|
setTitle("Wallet Name");
|
||||||
dialogPane.setHeaderText("Enter a name for this wallet:");
|
dialogPane.setHeaderText("Enter a name for this wallet:");
|
||||||
|
|
@ -121,16 +125,16 @@ public class WalletNameDialog extends Dialog<WalletNameDialog.NameAndBirthDate>
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
final ButtonType okButtonType = new javafx.scene.control.ButtonType("Create Wallet", ButtonBar.ButtonData.OK_DONE);
|
final ButtonType okButtonType = new javafx.scene.control.ButtonType((rename ? "Rename" : "Create") + " Wallet", ButtonBar.ButtonData.OK_DONE);
|
||||||
dialogPane.getButtonTypes().addAll(okButtonType);
|
dialogPane.getButtonTypes().addAll(okButtonType);
|
||||||
Button okButton = (Button) dialogPane.lookupButton(okButtonType);
|
Button okButton = (Button) dialogPane.lookupButton(okButtonType);
|
||||||
BooleanBinding isInvalid = Bindings.createBooleanBinding(() ->
|
BooleanBinding isInvalid = Bindings.createBooleanBinding(() ->
|
||||||
name.getText().length() == 0 || Storage.walletExists(name.getText()) || (existingCheck.isSelected() && existingPicker.getValue() == null), name.textProperty(), existingCheck.selectedProperty(), existingPicker.valueProperty());
|
name.getText().trim().length() == 0 || Storage.walletExists(name.getText()) || (existingCheck.isSelected() && existingPicker.getValue() == null), name.textProperty(), existingCheck.selectedProperty(), existingPicker.valueProperty());
|
||||||
okButton.disableProperty().bind(isInvalid);
|
okButton.disableProperty().bind(isInvalid);
|
||||||
|
|
||||||
name.setPromptText("Wallet Name");
|
name.setPromptText("Wallet Name");
|
||||||
Platform.runLater(name::requestFocus);
|
Platform.runLater(name::requestFocus);
|
||||||
setResultConverter(dialogButton -> dialogButton == okButtonType ? new NameAndBirthDate(name.getText(), existingPicker.getValue()) : null);
|
setResultConverter(dialogButton -> dialogButton == okButtonType ? new NameAndBirthDate(name.getText().trim(), existingPicker.getValue()) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class NameAndBirthDate {
|
public static class NameAndBirthDate {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
import com.sparrowwallet.sparrow.CurrencyRate;
|
||||||
|
import com.sparrowwallet.sparrow.io.Config;
|
||||||
|
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.Entry;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.Function;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.WalletTransactionsEntry;
|
||||||
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
|
import javafx.geometry.Side;
|
||||||
|
import javafx.scene.chart.NumberAxis;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class WalletSummaryDialog extends Dialog<Void> {
|
||||||
|
public WalletSummaryDialog(List<WalletForm> walletForms) {
|
||||||
|
if(walletForms.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("No wallets selected to summarize");
|
||||||
|
}
|
||||||
|
|
||||||
|
Wallet masterWallet = walletForms.get(0).getMasterWallet();
|
||||||
|
|
||||||
|
final DialogPane dialogPane = getDialogPane();
|
||||||
|
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
||||||
|
dialogPane.getStylesheets().add(AppServices.class.getResource("dialog.css").toExternalForm());
|
||||||
|
dialogPane.getStylesheets().add(AppServices.class.getResource("wallet/wallet.css").toExternalForm());
|
||||||
|
dialogPane.getStylesheets().add(AppServices.class.getResource("wallet/transactions.css").toExternalForm());
|
||||||
|
|
||||||
|
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
||||||
|
dialogPane.setHeaderText("Wallet Summary for " + masterWallet.getName());
|
||||||
|
|
||||||
|
Image image = new Image("image/sparrow-small.png", 50, 50, false, false);
|
||||||
|
if(!image.isError()) {
|
||||||
|
ImageView imageView = new ImageView();
|
||||||
|
imageView.setSmooth(false);
|
||||||
|
imageView.setImage(image);
|
||||||
|
dialogPane.setGraphic(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
HBox hBox = new HBox(40);
|
||||||
|
|
||||||
|
CoinTreeTable table = new CoinTreeTable();
|
||||||
|
|
||||||
|
TreeTableColumn<Entry, String> nameColumn = new TreeTableColumn<>("Wallet");
|
||||||
|
nameColumn.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, String> param) -> {
|
||||||
|
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getLabel());
|
||||||
|
});
|
||||||
|
nameColumn.setCellFactory(p -> new LabelCell());
|
||||||
|
table.getColumns().add(nameColumn);
|
||||||
|
|
||||||
|
TreeTableColumn<Entry, Number> balanceColumn = new TreeTableColumn<>("Balance");
|
||||||
|
balanceColumn.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Number> param) -> {
|
||||||
|
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getValue());
|
||||||
|
});
|
||||||
|
balanceColumn.setCellFactory(p -> new CoinCell());
|
||||||
|
table.getColumns().add(balanceColumn);
|
||||||
|
table.setUnitFormat(masterWallet);
|
||||||
|
|
||||||
|
CurrencyRate currencyRate = AppServices.getFiatCurrencyExchangeRate();
|
||||||
|
if(currencyRate != null && currencyRate.isAvailable() && Config.get().getExchangeSource() != ExchangeSource.NONE) {
|
||||||
|
TreeTableColumn<Entry, Number> fiatColumn = new TreeTableColumn<>(currencyRate.getCurrency().getSymbol());
|
||||||
|
fiatColumn.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Number> param) -> {
|
||||||
|
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getValue());
|
||||||
|
});
|
||||||
|
fiatColumn.setCellFactory(p -> new FiatCell());
|
||||||
|
table.getColumns().add(fiatColumn);
|
||||||
|
table.setCurrencyRate(currencyRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
SummaryEntry rootEntry = new SummaryEntry(walletForms);
|
||||||
|
TreeItem<Entry> rootItem = new TreeItem<>(rootEntry);
|
||||||
|
for(Entry childEntry : rootEntry.getChildren()) {
|
||||||
|
TreeItem<Entry> childItem = new TreeItem<>(childEntry);
|
||||||
|
rootItem.getChildren().add(childItem);
|
||||||
|
childItem.getChildren().add(new TreeItem<>(new UnconfirmedEntry((WalletTransactionsEntry)childEntry)));
|
||||||
|
}
|
||||||
|
|
||||||
|
table.setShowRoot(true);
|
||||||
|
table.setRoot(rootItem);
|
||||||
|
rootItem.setExpanded(true);
|
||||||
|
|
||||||
|
table.setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||||
|
table.setPrefWidth(450);
|
||||||
|
|
||||||
|
VBox vBox = new VBox();
|
||||||
|
vBox.getChildren().add(table);
|
||||||
|
|
||||||
|
hBox.getChildren().add(vBox);
|
||||||
|
|
||||||
|
NumberAxis xAxis = new NumberAxis();
|
||||||
|
xAxis.setSide(Side.BOTTOM);
|
||||||
|
xAxis.setForceZeroInRange(false);
|
||||||
|
xAxis.setMinorTickVisible(false);
|
||||||
|
NumberAxis yAxis = new NumberAxis();
|
||||||
|
yAxis.setSide(Side.LEFT);
|
||||||
|
BalanceChart balanceChart = new BalanceChart(xAxis, yAxis);
|
||||||
|
balanceChart.initialize(new WalletTransactionsEntry(masterWallet, true));
|
||||||
|
balanceChart.setAnimated(false);
|
||||||
|
balanceChart.setLegendVisible(false);
|
||||||
|
balanceChart.setVerticalGridLinesVisible(false);
|
||||||
|
|
||||||
|
hBox.getChildren().add(balanceChart);
|
||||||
|
|
||||||
|
getDialogPane().setContent(hBox);
|
||||||
|
|
||||||
|
ButtonType okButtonType = new javafx.scene.control.ButtonType("Done", ButtonBar.ButtonData.OK_DONE);
|
||||||
|
dialogPane.getButtonTypes().addAll(okButtonType);
|
||||||
|
|
||||||
|
AppServices.moveToActiveWindowScreen(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SummaryEntry extends Entry {
|
||||||
|
private SummaryEntry(List<WalletForm> walletForms) {
|
||||||
|
super(walletForms.get(0).getWallet(), walletForms.get(0).getWallet().getName(), walletForms.stream().map(WalletForm::getWalletTransactionsEntry).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getValue() {
|
||||||
|
long value = 0;
|
||||||
|
for(Entry entry : getChildren()) {
|
||||||
|
value += entry.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEntryType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Function getWalletFunction() {
|
||||||
|
return Function.TRANSACTIONS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class UnconfirmedEntry extends Entry {
|
||||||
|
private final WalletTransactionsEntry walletTransactionsEntry;
|
||||||
|
|
||||||
|
private UnconfirmedEntry(WalletTransactionsEntry walletTransactionsEntry) {
|
||||||
|
super(walletTransactionsEntry.getWallet(), "Unconfirmed", Collections.emptyList());
|
||||||
|
this.walletTransactionsEntry = walletTransactionsEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getValue() {
|
||||||
|
return walletTransactionsEntry.getMempoolBalance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getEntryType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Function getWalletFunction() {
|
||||||
|
return Function.TRANSACTIONS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ import javafx.concurrent.ScheduledService;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.embed.swing.SwingFXUtils;
|
import javafx.embed.swing.SwingFXUtils;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
|
import net.sourceforge.zbar.ZBar;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
@ -155,6 +156,13 @@ public class WebcamService extends ScheduledService<Image> {
|
||||||
LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
|
LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
|
||||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||||
|
|
||||||
|
if(ZBar.isEnabled()) {
|
||||||
|
ZBar.Scan scan = ZBar.scan(bufferedImage);
|
||||||
|
if(scan != null) {
|
||||||
|
return new Result(scan.stringData(), scan.rawData(), new ResultPoint[0], BarcodeFormat.QR_CODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return qrReader.decode(bitmap, Map.of(DecodeHintType.TRY_HARDER, Boolean.TRUE));
|
return qrReader.decode(bitmap, Map.of(DecodeHintType.TRY_HARDER, Boolean.TRUE));
|
||||||
} catch(ReaderException e) {
|
} catch(ReaderException e) {
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ public class Config {
|
||||||
private int enumerateHwPeriod = ENUMERATE_HW_PERIOD_SECS;
|
private int enumerateHwPeriod = ENUMERATE_HW_PERIOD_SECS;
|
||||||
private QRDensity qrDensity;
|
private QRDensity qrDensity;
|
||||||
private Boolean hdCapture;
|
private Boolean hdCapture;
|
||||||
|
private boolean useZbar = true;
|
||||||
private String webcamDevice;
|
private String webcamDevice;
|
||||||
private ServerType serverType;
|
private ServerType serverType;
|
||||||
private Server publicElectrumServer;
|
private Server publicElectrumServer;
|
||||||
|
|
@ -77,6 +78,7 @@ public class Config {
|
||||||
private int maxPageSize = DEFAULT_PAGE_SIZE;
|
private int maxPageSize = DEFAULT_PAGE_SIZE;
|
||||||
private boolean usePayNym;
|
private boolean usePayNym;
|
||||||
private boolean sameAppMixing;
|
private boolean sameAppMixing;
|
||||||
|
private boolean mempoolFullRbf;
|
||||||
private Double appWidth;
|
private Double appWidth;
|
||||||
private Double appHeight;
|
private Double appHeight;
|
||||||
|
|
||||||
|
|
@ -404,6 +406,10 @@ public class Config {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isUseZbar() {
|
||||||
|
return useZbar;
|
||||||
|
}
|
||||||
|
|
||||||
public String getWebcamDevice() {
|
public String getWebcamDevice() {
|
||||||
return webcamDevice;
|
return webcamDevice;
|
||||||
}
|
}
|
||||||
|
|
@ -665,6 +671,15 @@ public class Config {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isMempoolFullRbf() {
|
||||||
|
return mempoolFullRbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMempoolFullRbf(boolean mempoolFullRbf) {
|
||||||
|
this.mempoolFullRbf = mempoolFullRbf;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
public Double getAppWidth() {
|
public Double getAppWidth() {
|
||||||
return appWidth;
|
return appWidth;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public class Hwi {
|
||||||
private static final Logger log = LoggerFactory.getLogger(Hwi.class);
|
private static final Logger log = LoggerFactory.getLogger(Hwi.class);
|
||||||
private static final String HWI_HOME_DIR = "hwi";
|
private static final String HWI_HOME_DIR = "hwi";
|
||||||
private static final String HWI_VERSION_PREFIX = "hwi-";
|
private static final String HWI_VERSION_PREFIX = "hwi-";
|
||||||
private static final String HWI_VERSION = "2.2.1";
|
private static final String HWI_VERSION = "2.3.1";
|
||||||
private static final String HWI_VERSION_DIR = HWI_VERSION_PREFIX + HWI_VERSION;
|
private static final String HWI_VERSION_DIR = HWI_VERSION_PREFIX + HWI_VERSION;
|
||||||
|
|
||||||
private static boolean isPromptActive = false;
|
private static boolean isPromptActive = false;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
package com.sparrowwallet.sparrow.io;
|
package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class JadeMultisig extends ColdcardMultisig {
|
public class JadeMultisig extends ColdcardMultisig {
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
|
@ -38,4 +41,30 @@ public class JadeMultisig extends ColdcardMultisig {
|
||||||
public boolean walletExportRequiresDecryption() {
|
public boolean walletExportRequiresDecryption() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWalletImportDescription() {
|
||||||
|
return "Import the QR created using Options > Wallet > Registered Wallets on your Jade.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWalletImportScannable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWalletImportFileFormatAvailable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Wallet importWallet(InputStream inputStream, String password) throws ImportException {
|
||||||
|
Wallet wallet = super.importWallet(inputStream, password);
|
||||||
|
for(Keystore keystore : wallet.getKeystores()) {
|
||||||
|
keystore.setLabel(keystore.getLabel().replace("Coldcard", "Jade"));
|
||||||
|
keystore.setWalletModel(WalletModel.JADE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,10 @@ public class Sparrow implements WalletImport, WalletExport {
|
||||||
Storage tempStorage = new Storage(persistence, tempFile);
|
Storage tempStorage = new Storage(persistence, tempFile);
|
||||||
tempStorage.setKeyDeriver(storage.getKeyDeriver());
|
tempStorage.setKeyDeriver(storage.getKeyDeriver());
|
||||||
tempStorage.setEncryptionPubKey(storage.getEncryptionPubKey());
|
tempStorage.setEncryptionPubKey(storage.getEncryptionPubKey());
|
||||||
tempStorage.saveWallet(exportedWallet);
|
|
||||||
for(Wallet childWallet : exportedWallet.getChildWallets()) {
|
Wallet copy = exportedWallet.copy();
|
||||||
|
tempStorage.saveWallet(copy);
|
||||||
|
for(Wallet childWallet : copy.getChildWallets()) {
|
||||||
tempStorage.saveWallet(childWallet);
|
tempStorage.saveWallet(childWallet);
|
||||||
}
|
}
|
||||||
persistence.close();
|
persistence.close();
|
||||||
|
|
|
||||||
|
|
@ -265,8 +265,11 @@ public class Storage {
|
||||||
persistence.copyWallet(walletFile, outputStream);
|
persistence.copyWallet(walletFile, outputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean delete() {
|
public boolean delete(boolean deleteBackups) {
|
||||||
deleteBackups();
|
if(deleteBackups) {
|
||||||
|
deleteBackups();
|
||||||
|
}
|
||||||
|
|
||||||
return IOUtils.secureDelete(walletFile);
|
return IOUtils.secureDelete(walletFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -735,18 +738,44 @@ public class Storage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class CopyWalletService extends Service<Void> {
|
||||||
|
private final Wallet wallet;
|
||||||
|
private final File newWalletFile;
|
||||||
|
|
||||||
|
public CopyWalletService(Wallet wallet, File newWalletFile) {
|
||||||
|
this.wallet = wallet;
|
||||||
|
this.newWalletFile = newWalletFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<Void> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
protected Void call() throws IOException, ExportException {
|
||||||
|
Sparrow export = new Sparrow();
|
||||||
|
try(BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(newWalletFile))) {
|
||||||
|
export.exportWallet(wallet, outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class DeleteWalletService extends ScheduledService<Boolean> {
|
public static class DeleteWalletService extends ScheduledService<Boolean> {
|
||||||
private final Storage storage;
|
private final Storage storage;
|
||||||
|
private final boolean deleteBackups;
|
||||||
|
|
||||||
public DeleteWalletService(Storage storage) {
|
public DeleteWalletService(Storage storage, boolean deleteBackups) {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
|
this.deleteBackups = deleteBackups;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Task<Boolean> createTask() {
|
protected Task<Boolean> createTask() {
|
||||||
return new Task<>() {
|
return new Task<>() {
|
||||||
protected Boolean call() {
|
protected Boolean call() {
|
||||||
return storage.delete();
|
return storage.delete(deleteBackups);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,7 @@ public interface WalletImport extends FileImport {
|
||||||
String getWalletImportDescription();
|
String getWalletImportDescription();
|
||||||
Wallet importWallet(InputStream inputStream, String password) throws ImportException;
|
Wallet importWallet(InputStream inputStream, String password) throws ImportException;
|
||||||
boolean isWalletImportScannable();
|
boolean isWalletImportScannable();
|
||||||
|
default boolean isWalletImportFileFormatAvailable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -583,7 +583,7 @@ public class DbPersistence implements Persistence {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isClosed() {
|
public boolean isClosed() {
|
||||||
return dataSource.isClosed();
|
return dataSource == null || dataSource.isClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ public interface MixConfigDao {
|
||||||
|
|
||||||
default void addMixConfig(Wallet wallet) {
|
default void addMixConfig(Wallet wallet) {
|
||||||
if(wallet.getMixConfig() != null) {
|
if(wallet.getMixConfig() != null) {
|
||||||
|
wallet.getMixConfig().setId(null);
|
||||||
addOrUpdate(wallet, wallet.getMixConfig());
|
addOrUpdate(wallet, wallet.getMixConfig());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,9 +96,8 @@ public final class IpAddressMatcher {
|
||||||
private InetAddress parseAddress(String address) {
|
private InetAddress parseAddress(String address) {
|
||||||
try {
|
try {
|
||||||
return InetAddress.getByName(address);
|
return InetAddress.getByName(address);
|
||||||
}
|
} catch(UnknownHostException e) {
|
||||||
catch (UnknownHostException e) {
|
throw new IllegalArgumentException("Failed to resolve address: " + address, e);
|
||||||
throw new IllegalArgumentException("Failed to parse address: " + address, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ public enum PublicElectrumServer {
|
||||||
EMZY_DE("electrum.emzy.de", "ssl://electrum.emzy.de:50002", Network.MAINNET),
|
EMZY_DE("electrum.emzy.de", "ssl://electrum.emzy.de:50002", Network.MAINNET),
|
||||||
BITAROO_NET("electrum.bitaroo.net", "ssl://electrum.bitaroo.net:50002", Network.MAINNET),
|
BITAROO_NET("electrum.bitaroo.net", "ssl://electrum.bitaroo.net:50002", Network.MAINNET),
|
||||||
DIYNODES_COM("electrum.diynodes.com", "ssl://electrum.diynodes.com:50022", Network.MAINNET),
|
DIYNODES_COM("electrum.diynodes.com", "ssl://electrum.diynodes.com:50022", Network.MAINNET),
|
||||||
|
SETHFORPRIVACY_COM("fulcrum.sethforprivacy.com", "ssl://fulcrum.sethforprivacy.com:50002", Network.MAINNET),
|
||||||
TESTNET_ARANGUREN_ORG("testnet.aranguren.org", "ssl://testnet.aranguren.org:51002", Network.TESTNET);
|
TESTNET_ARANGUREN_ORG("testnet.aranguren.org", "ssl://testnet.aranguren.org:51002", Network.TESTNET);
|
||||||
|
|
||||||
PublicElectrumServer(String name, String url, Network network) {
|
PublicElectrumServer(String name, String url, Network network) {
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,10 @@ public class Tor {
|
||||||
dormantCanceledByStartup.set(TorConfig.Option.AorTorF.getTrue());
|
dormantCanceledByStartup.set(TorConfig.Option.AorTorF.getTrue());
|
||||||
builder.put(dormantCanceledByStartup);
|
builder.put(dormantCanceledByStartup);
|
||||||
|
|
||||||
|
TorConfig.Setting.Ports.Control controlPort = new TorConfig.Setting.Ports.Control();
|
||||||
|
controlPort.set(TorConfig.Option.AorDorPort.Auto.INSTANCE);
|
||||||
|
builder.put(controlPort);
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -141,11 +141,13 @@ public class BitcoindClient {
|
||||||
boolean exists = listWalletDirResult.wallets().stream().anyMatch(walletDirResult -> walletDirResult.name().equals(CORE_WALLET_NAME));
|
boolean exists = listWalletDirResult.wallets().stream().anyMatch(walletDirResult -> walletDirResult.name().equals(CORE_WALLET_NAME));
|
||||||
legacyWalletExists = listWalletDirResult.wallets().stream().anyMatch(walletDirResult -> walletDirResult.name().equals(Bwt.DEFAULT_CORE_WALLET));
|
legacyWalletExists = listWalletDirResult.wallets().stream().anyMatch(walletDirResult -> walletDirResult.name().equals(Bwt.DEFAULT_CORE_WALLET));
|
||||||
|
|
||||||
if(!exists) {
|
List<String> loadedWallets = getBitcoindService().listWallets();
|
||||||
|
boolean loaded = loadedWallets.contains(CORE_WALLET_NAME);
|
||||||
|
|
||||||
|
if(!exists && !loaded) {
|
||||||
getBitcoindService().createWallet(CORE_WALLET_NAME, true, true, "", true, true, true, false);
|
getBitcoindService().createWallet(CORE_WALLET_NAME, true, true, "", true, true, true, false);
|
||||||
} else {
|
} else {
|
||||||
List<String> wallets = getBitcoindService().listWallets();
|
if(!loaded) {
|
||||||
if(!wallets.contains(CORE_WALLET_NAME)) {
|
|
||||||
getBitcoindService().loadWallet(CORE_WALLET_NAME, true);
|
getBitcoindService().loadWallet(CORE_WALLET_NAME, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
@ -45,6 +46,9 @@ public class GeneralPreferencesController extends PreferencesDetailController {
|
||||||
@FXML
|
@FXML
|
||||||
private ComboBox<ExchangeSource> exchangeSource;
|
private ComboBox<ExchangeSource> exchangeSource;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label currenciesLoadWarning;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private UnlabeledToggleSwitch loadRecentWallets;
|
private UnlabeledToggleSwitch loadRecentWallets;
|
||||||
|
|
||||||
|
|
@ -87,6 +91,9 @@ public class GeneralPreferencesController extends PreferencesDetailController {
|
||||||
EventManager.get().post(new FeeRatesSourceChangedEvent(newValue));
|
EventManager.get().post(new FeeRatesSourceChangedEvent(newValue));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
currenciesLoadWarning.managedProperty().bind(currenciesLoadWarning.visibleProperty());
|
||||||
|
currenciesLoadWarning.setVisible(false);
|
||||||
|
|
||||||
blockExplorers.setItems(getBlockExplorerList());
|
blockExplorers.setItems(getBlockExplorerList());
|
||||||
blockExplorers.setConverter(new StringConverter<>() {
|
blockExplorers.setConverter(new StringConverter<>() {
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -237,6 +244,8 @@ public class GeneralPreferencesController extends PreferencesDetailController {
|
||||||
fiatCurrency.setDisable(true);
|
fiatCurrency.setDisable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currenciesLoadWarning.setVisible(exchangeSource.getValue() != ExchangeSource.NONE && currencies.isEmpty());
|
||||||
|
|
||||||
//Always fire event regardless of previous selection to update rates
|
//Always fire event regardless of previous selection to update rates
|
||||||
EventManager.get().post(new FiatCurrencySelectedEvent(exchangeSource.getValue(), fiatCurrency.getValue()));
|
EventManager.get().post(new FiatCurrencySelectedEvent(exchangeSource.getValue(), fiatCurrency.getValue()));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import java.util.stream.Collectors;
|
||||||
public class ServerAliasDialog extends Dialog<Server> {
|
public class ServerAliasDialog extends Dialog<Server> {
|
||||||
private final ServerType serverType;
|
private final ServerType serverType;
|
||||||
private final TableView<ServerEntry> serverTable;
|
private final TableView<ServerEntry> serverTable;
|
||||||
|
private final Button closeButton;
|
||||||
|
|
||||||
public ServerAliasDialog(ServerType serverType) {
|
public ServerAliasDialog(ServerType serverType) {
|
||||||
this.serverType = serverType;
|
this.serverType = serverType;
|
||||||
|
|
@ -76,6 +77,7 @@ public class ServerAliasDialog extends Dialog<Server> {
|
||||||
|
|
||||||
Button selectButton = (Button)dialogPane.lookupButton(selectButtonType);
|
Button selectButton = (Button)dialogPane.lookupButton(selectButtonType);
|
||||||
Button deleteButton = (Button)dialogPane.lookupButton(deleteButtonType);
|
Button deleteButton = (Button)dialogPane.lookupButton(deleteButtonType);
|
||||||
|
closeButton = (Button)dialogPane.lookupButton(ButtonType.CLOSE);
|
||||||
selectButton.setDefaultButton(true);
|
selectButton.setDefaultButton(true);
|
||||||
selectButton.setDisable(true);
|
selectButton.setDisable(true);
|
||||||
deleteButton.setDisable(true);
|
deleteButton.setDisable(true);
|
||||||
|
|
@ -112,8 +114,14 @@ public class ServerAliasDialog extends Dialog<Server> {
|
||||||
serverTable.getItems().remove(serverEntry);
|
serverTable.getItems().remove(serverEntry);
|
||||||
if(serverType == ServerType.BITCOIN_CORE) {
|
if(serverType == ServerType.BITCOIN_CORE) {
|
||||||
Config.get().removeRecentCoreServer(serverEntry.getServer());
|
Config.get().removeRecentCoreServer(serverEntry.getServer());
|
||||||
|
if(serverEntry.getServer().equals(Config.get().getCoreServer()) && !serverTable.getItems().isEmpty()) {
|
||||||
|
closeButton.setDisable(true);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Config.get().removeRecentElectrumServer(serverEntry.getServer());
|
Config.get().removeRecentElectrumServer(serverEntry.getServer());
|
||||||
|
if(serverEntry.getServer().equals(Config.get().getElectrumServer()) && !serverTable.getItems().isEmpty()) {
|
||||||
|
closeButton.setDisable(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ public class PublicElectrumDialog extends ServerProxyDialog {
|
||||||
url.setSelectedItem(PublicElectrumServer.fromServer(Config.get().getPublicElectrumServer()));
|
url.setSelectedItem(PublicElectrumServer.fromServer(Config.get().getPublicElectrumServer()));
|
||||||
url.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> {
|
url.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> {
|
||||||
if(selectedIndex != previousSelection) {
|
if(selectedIndex != previousSelection) {
|
||||||
Config.get().setPublicElectrumServer(PublicElectrumServer.values()[selectedIndex].getServer());
|
Config.get().setPublicElectrumServer(PublicElectrumServer.getServers().get(selectedIndex).getServer());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mainPanel.addComponent(url);
|
mainPanel.addComponent(url);
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,9 @@ public class UtxosDialog extends WalletDialog {
|
||||||
SparrowTerminal.get().getGuiThread().invokeLater(() -> {
|
SparrowTerminal.get().getGuiThread().invokeLater(() -> {
|
||||||
TableModel<TableCell> tableModel = getTableModel(walletUtxosEntry);
|
TableModel<TableCell> tableModel = getTableModel(walletUtxosEntry);
|
||||||
utxos.setTableModel(tableModel);
|
utxos.setTableModel(tableModel);
|
||||||
|
if(utxos.getTheme() != null && utxos.getRenderer().getViewTopRow() >= tableModel.getRowCount()) {
|
||||||
|
utxos.getRenderer().setViewTopRow(0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
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)),
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Label is too long", newValue.replace(" ", "").length() > 16)
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Label is too long", newValue.replace(" ", "").length() > Keystore.MAX_LABEL_LENGTH)
|
||||||
));
|
));
|
||||||
|
|
||||||
validationSupport.registerValidator(xpub, Validator.combine(
|
validationSupport.registerValidator(xpub, Validator.combine(
|
||||||
|
|
@ -551,6 +551,9 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
fingerprint.setText(keyDerivation.getMasterFingerprint());
|
fingerprint.setText(keyDerivation.getMasterFingerprint());
|
||||||
derivation.setText(keyDerivation.getDerivationPath());
|
derivation.setText(keyDerivation.getDerivationPath());
|
||||||
xpub.setText(extendedKey.toString());
|
xpub.setText(extendedKey.toString());
|
||||||
|
if(result.outputDescriptor.getExtendedPublicKeyLabel(extendedKey) != null) {
|
||||||
|
label.setText(result.outputDescriptor.getExtendedPublicKeyLabel(extendedKey));
|
||||||
|
}
|
||||||
} else if(result.wallets != null) {
|
} else if(result.wallets != null) {
|
||||||
for(Wallet wallet : result.wallets) {
|
for(Wallet wallet : result.wallets) {
|
||||||
if(getWalletForm().getWallet().getScriptType().equals(wallet.getScriptType()) && !wallet.getKeystores().isEmpty()) {
|
if(getWalletForm().getWallet().getScriptType().equals(wallet.getScriptType()) && !wallet.getKeystores().isEmpty()) {
|
||||||
|
|
@ -558,6 +561,9 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
fingerprint.setText(keystore.getKeyDerivation().getMasterFingerprint());
|
fingerprint.setText(keystore.getKeyDerivation().getMasterFingerprint());
|
||||||
derivation.setText(keystore.getKeyDerivation().getDerivationPath());
|
derivation.setText(keystore.getKeyDerivation().getDerivationPath());
|
||||||
xpub.setText(keystore.getExtendedPublicKey().toString());
|
xpub.setText(keystore.getExtendedPublicKey().toString());
|
||||||
|
if(!Keystore.DEFAULT_LABEL.equals(keystore.getLabel())) {
|
||||||
|
label.setText(keystore.getLabel());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||||
|
import static com.sparrowwallet.sparrow.AppServices.showWarningDialog;
|
||||||
|
|
||||||
public class SettingsController extends WalletFormController implements Initializable {
|
public class SettingsController extends WalletFormController implements Initializable {
|
||||||
private static final Logger log = LoggerFactory.getLogger(SettingsController.class);
|
private static final Logger log = LoggerFactory.getLogger(SettingsController.class);
|
||||||
|
|
@ -97,6 +98,7 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
private final SimpleIntegerProperty totalKeystores = new SimpleIntegerProperty(0);
|
private final SimpleIntegerProperty totalKeystores = new SimpleIntegerProperty(0);
|
||||||
|
|
||||||
private boolean initialising = true;
|
private boolean initialising = true;
|
||||||
|
private boolean reverting;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
|
@ -141,9 +143,32 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
scriptType.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, scriptType) -> {
|
scriptType.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
if(scriptType != null) {
|
if(newValue != null) {
|
||||||
walletForm.getWallet().setScriptType(scriptType);
|
if(oldValue != null && !reverting && walletForm.getWallet().getKeystores().stream().anyMatch(keystore -> keystore.getExtendedPublicKey() != null)) {
|
||||||
|
Optional<ButtonType> optType = showWarningDialog("Clear keystores?",
|
||||||
|
"You are changing the script type on a wallet with existing key information. Usually this means the keys need to be re-imported using a different derivation path.\n\n" +
|
||||||
|
"Do you want to clear the current key information?", ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
|
||||||
|
if(optType.isPresent()) {
|
||||||
|
if(optType.get() == ButtonType.CANCEL) {
|
||||||
|
reverting = true;
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
scriptType.getSelectionModel().select(oldValue);
|
||||||
|
reverting = false;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else if(optType.get() == ButtonType.YES) {
|
||||||
|
clearKeystoreTabs();
|
||||||
|
if(walletForm.getWallet().getPolicyType() == PolicyType.MULTI) {
|
||||||
|
totalKeystores.bind(multisigControl.highValueProperty());
|
||||||
|
} else {
|
||||||
|
totalKeystores.set(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
walletForm.getWallet().setScriptType(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.SCRIPT_TYPE));
|
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.SCRIPT_TYPE));
|
||||||
|
|
@ -215,7 +240,10 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
totalKeystores.setValue(0);
|
totalKeystores.setValue(0);
|
||||||
walletForm.revert();
|
walletForm.revert();
|
||||||
initialising = true;
|
initialising = true;
|
||||||
|
reverting = true;
|
||||||
setFieldsFromWallet(walletForm.getWallet());
|
setFieldsFromWallet(walletForm.getWallet());
|
||||||
|
reverting = false;
|
||||||
|
initialising = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
apply.setOnAction(event -> {
|
apply.setOnAction(event -> {
|
||||||
|
|
@ -313,12 +341,12 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
if(optionalResult.isPresent()) {
|
if(optionalResult.isPresent()) {
|
||||||
QRScanDialog.Result result = optionalResult.get();
|
QRScanDialog.Result result = optionalResult.get();
|
||||||
if(result.outputDescriptor != null) {
|
if(result.outputDescriptor != null) {
|
||||||
setDescriptorText(result.outputDescriptor.toString());
|
replaceWallet(result.outputDescriptor.toWallet());
|
||||||
} else if(result.wallets != null) {
|
} else if(result.wallets != null) {
|
||||||
for(Wallet wallet : result.wallets) {
|
for(Wallet wallet : result.wallets) {
|
||||||
if(scriptType.getValue().equals(wallet.getScriptType()) && !wallet.getKeystores().isEmpty()) {
|
if(scriptType.getValue().equals(wallet.getScriptType()) && !wallet.getKeystores().isEmpty()) {
|
||||||
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(wallet);
|
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(wallet);
|
||||||
setDescriptorText(outputDescriptor.toString());
|
replaceWallet(outputDescriptor.toWallet());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -397,7 +425,7 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
CryptoCoinInfo cryptoCoinInfo = new CryptoCoinInfo(CryptoCoinInfo.Type.BITCOIN.ordinal(), Network.get() == Network.MAINNET ? CryptoCoinInfo.Network.MAINNET.ordinal() : CryptoCoinInfo.Network.TESTNET.ordinal());
|
CryptoCoinInfo cryptoCoinInfo = new CryptoCoinInfo(CryptoCoinInfo.Type.BITCOIN.ordinal(), Network.get() == Network.MAINNET ? CryptoCoinInfo.Network.MAINNET.ordinal() : CryptoCoinInfo.Network.TESTNET.ordinal());
|
||||||
List<PathComponent> pathComponents = keystore.getKeyDerivation().getDerivation().stream().map(cNum -> new IndexPathComponent(cNum.num(), cNum.isHardened())).collect(Collectors.toList());
|
List<PathComponent> pathComponents = keystore.getKeyDerivation().getDerivation().stream().map(cNum -> new IndexPathComponent(cNum.num(), cNum.isHardened())).collect(Collectors.toList());
|
||||||
CryptoKeypath cryptoKeypath = new CryptoKeypath(pathComponents, Utils.hexToBytes(keystore.getKeyDerivation().getMasterFingerprint()), pathComponents.size());
|
CryptoKeypath cryptoKeypath = new CryptoKeypath(pathComponents, Utils.hexToBytes(keystore.getKeyDerivation().getMasterFingerprint()), pathComponents.size());
|
||||||
return new CryptoHDKey(false, extendedKey.getKey().getPubKey(), extendedKey.getKey().getChainCode(), cryptoCoinInfo, cryptoKeypath, null, extendedKey.getParentFingerprint());
|
return new CryptoHDKey(false, extendedKey.getKey().getPubKey(), extendedKey.getKey().getChainCode(), cryptoCoinInfo, cryptoKeypath, null, extendedKey.getParentFingerprint(), keystore.getLabel(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void editDescriptor(ActionEvent event) {
|
public void editDescriptor(ActionEvent event) {
|
||||||
|
|
|
||||||
|
|
@ -506,16 +506,22 @@ public class WalletForm {
|
||||||
for(WalletNode childNode : wallet.getNode(keyPurpose).getChildren()) {
|
for(WalletNode childNode : wallet.getNode(keyPurpose).getChildren()) {
|
||||||
for(BlockTransactionHashIndex receivedRef : childNode.getTransactionOutputs()) {
|
for(BlockTransactionHashIndex receivedRef : childNode.getTransactionOutputs()) {
|
||||||
if(receivedRef.getHash().equals(transactionEntry.getBlockTransaction().getHash())) {
|
if(receivedRef.getHash().equals(transactionEntry.getBlockTransaction().getHash())) {
|
||||||
if((receivedRef.getLabel() == null || receivedRef.getLabel().isEmpty()) && wallet.getStandardAccountType() != StandardAccount.WHIRLPOOL_PREMIX) {
|
String prevRefLabel = "";
|
||||||
|
if((receivedRef.getLabel() == null || receivedRef.getLabel().isEmpty()
|
||||||
|
|| receivedRef.getLabel().endsWith(" (sent)") || receivedRef.getLabel().endsWith(" (change)") || receivedRef.getLabel().endsWith(" (received)"))
|
||||||
|
&& wallet.getStandardAccountType() != StandardAccount.WHIRLPOOL_PREMIX) {
|
||||||
|
prevRefLabel = receivedRef.getLabel() == null ? "" : receivedRef.getLabel();
|
||||||
receivedRef.setLabel(entry.getLabel() + (keyPurpose == KeyPurpose.CHANGE ? (event.getWallet().isBip47() ? " (sent)" : " (change)") : " (received)"));
|
receivedRef.setLabel(entry.getLabel() + (keyPurpose == KeyPurpose.CHANGE ? (event.getWallet().isBip47() ? " (sent)" : " (change)") : " (received)"));
|
||||||
labelChangedEntries.put(new HashIndexEntry(event.getWallet(), receivedRef, HashIndexEntry.Type.OUTPUT, keyPurpose), entry);
|
labelChangedEntries.put(new HashIndexEntry(event.getWallet(), receivedRef, HashIndexEntry.Type.OUTPUT, keyPurpose), entry);
|
||||||
}
|
}
|
||||||
if((childNode.getLabel() == null || childNode.getLabel().isEmpty())) {
|
if(childNode.getLabel() == null || childNode.getLabel().isEmpty()
|
||||||
|
|| prevRefLabel.equals(childNode.getLabel() + " (sent)") || prevRefLabel.equals(childNode.getLabel() + " (change)") || prevRefLabel.equals(childNode.getLabel() + " (received)")) {
|
||||||
childNode.setLabel(entry.getLabel());
|
childNode.setLabel(entry.getLabel());
|
||||||
labelChangedEntries.put(new NodeEntry(event.getWallet(), childNode), entry);
|
labelChangedEntries.put(new NodeEntry(event.getWallet(), childNode), entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(receivedRef.isSpent() && receivedRef.getSpentBy().getHash().equals(transactionEntry.getBlockTransaction().getHash()) && (receivedRef.getSpentBy().getLabel() == null || receivedRef.getSpentBy().getLabel().isEmpty())) {
|
if(receivedRef.isSpent() && receivedRef.getSpentBy().getHash().equals(transactionEntry.getBlockTransaction().getHash())
|
||||||
|
&& (receivedRef.getSpentBy().getLabel() == null || receivedRef.getSpentBy().getLabel().isEmpty() || receivedRef.getSpentBy().getLabel().endsWith(" (input)"))) {
|
||||||
receivedRef.getSpentBy().setLabel(entry.getLabel() + " (input)");
|
receivedRef.getSpentBy().setLabel(entry.getLabel() + " (input)");
|
||||||
labelChangedEntries.put(new HashIndexEntry(event.getWallet(), receivedRef.getSpentBy(), HashIndexEntry.Type.INPUT, keyPurpose), entry);
|
labelChangedEntries.put(new HashIndexEntry(event.getWallet(), receivedRef.getSpentBy(), HashIndexEntry.Type.INPUT, keyPurpose), entry);
|
||||||
}
|
}
|
||||||
|
|
@ -591,6 +597,10 @@ public class WalletForm {
|
||||||
public void walletLabelChanged(WalletLabelChangedEvent event) {
|
public void walletLabelChanged(WalletLabelChangedEvent event) {
|
||||||
if(event.getWallet() == wallet) {
|
if(event.getWallet() == wallet) {
|
||||||
Platform.runLater(() -> EventManager.get().post(new WalletDataChangedEvent(wallet)));
|
Platform.runLater(() -> EventManager.get().post(new WalletDataChangedEvent(wallet)));
|
||||||
|
|
||||||
|
if(walletTransactionsEntry != null) {
|
||||||
|
walletTransactionsEntry.labelProperty().set(event.getWallet().getDisplayName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,11 @@ public class WalletTransactionsEntry extends Entry {
|
||||||
private static final Logger log = LoggerFactory.getLogger(WalletTransactionsEntry.class);
|
private static final Logger log = LoggerFactory.getLogger(WalletTransactionsEntry.class);
|
||||||
|
|
||||||
public WalletTransactionsEntry(Wallet wallet) {
|
public WalletTransactionsEntry(Wallet wallet) {
|
||||||
super(wallet, wallet.getName(), getWalletTransactions(wallet).stream().map(WalletTransaction::getTransactionEntry).collect(Collectors.toList()));
|
this(wallet, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WalletTransactionsEntry(Wallet wallet, boolean includeAllChildWallets) {
|
||||||
|
super(wallet, wallet.getDisplayName(), getWalletTransactions(wallet, includeAllChildWallets).stream().map(WalletTransaction::getTransactionEntry).collect(Collectors.toList()));
|
||||||
calculateBalances(false); //No need to resort
|
calculateBalances(false); //No need to resort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,7 +77,7 @@ public class WalletTransactionsEntry extends Entry {
|
||||||
.collect(Collectors.toUnmodifiableMap(entry -> new HashIndex(entry.getKey().getHash(), entry.getKey().getIndex()), Map.Entry::getKey,
|
.collect(Collectors.toUnmodifiableMap(entry -> new HashIndex(entry.getKey().getHash(), entry.getKey().getIndex()), Map.Entry::getKey,
|
||||||
BinaryOperator.maxBy(BlockTransactionHashIndex::compareTo)));
|
BinaryOperator.maxBy(BlockTransactionHashIndex::compareTo)));
|
||||||
|
|
||||||
Collection<WalletTransactionsEntry.WalletTransaction> entries = getWalletTransactions(getWallet());
|
Collection<WalletTransactionsEntry.WalletTransaction> entries = getWalletTransactions(getWallet(), false);
|
||||||
Set<Entry> current = entries.stream().map(WalletTransaction::getTransactionEntry).collect(Collectors.toCollection(LinkedHashSet::new));
|
Set<Entry> current = entries.stream().map(WalletTransaction::getTransactionEntry).collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
Set<Entry> previous = new LinkedHashSet<>(getChildren());
|
Set<Entry> previous = new LinkedHashSet<>(getChildren());
|
||||||
|
|
||||||
|
|
@ -101,7 +105,7 @@ public class WalletTransactionsEntry extends Entry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Collection<WalletTransaction> getWalletTransactions(Wallet wallet) {
|
private static Collection<WalletTransaction> getWalletTransactions(Wallet wallet, boolean includeAllChildWallets) {
|
||||||
Map<BlockTransaction, WalletTransaction> walletTransactionMap = new HashMap<>(wallet.getTransactions().size());
|
Map<BlockTransaction, WalletTransaction> walletTransactionMap = new HashMap<>(wallet.getTransactions().size());
|
||||||
|
|
||||||
for(KeyPurpose keyPurpose : wallet.getWalletKeyPurposes()) {
|
for(KeyPurpose keyPurpose : wallet.getWalletKeyPurposes()) {
|
||||||
|
|
@ -109,7 +113,7 @@ public class WalletTransactionsEntry extends Entry {
|
||||||
}
|
}
|
||||||
|
|
||||||
for(Wallet childWallet : wallet.getChildWallets()) {
|
for(Wallet childWallet : wallet.getChildWallets()) {
|
||||||
if(childWallet.isNested()) {
|
if(includeAllChildWallets || childWallet.isNested()) {
|
||||||
for(KeyPurpose keyPurpose : childWallet.getWalletKeyPurposes()) {
|
for(KeyPurpose keyPurpose : childWallet.getWalletKeyPurposes()) {
|
||||||
getWalletTransactions(childWallet, walletTransactionMap, childWallet.getNode(keyPurpose));
|
getWalletTransactions(childWallet, walletTransactionMap, childWallet.getNode(keyPurpose));
|
||||||
}
|
}
|
||||||
|
|
@ -218,6 +222,14 @@ public class WalletTransactionsEntry extends Entry {
|
||||||
return mempoolBalance;
|
return mempoolBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof WalletTransactionsEntry)) return false;
|
||||||
|
|
||||||
|
return super.equals(o);
|
||||||
|
}
|
||||||
|
|
||||||
private static class WalletTransaction implements Comparable<WalletTransaction> {
|
private static class WalletTransaction implements Comparable<WalletTransaction> {
|
||||||
private final Wallet wallet;
|
private final Wallet wallet;
|
||||||
private final BlockTransaction blockTransaction;
|
private final BlockTransaction blockTransaction;
|
||||||
|
|
|
||||||
76
src/main/java/net/sourceforge/zbar/Config.java
Normal file
76
src/main/java/net/sourceforge/zbar/Config.java
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* Config
|
||||||
|
*
|
||||||
|
* Copyright 2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoder configuration options.
|
||||||
|
*/
|
||||||
|
public class Config {
|
||||||
|
/**
|
||||||
|
* Enable symbology/feature.
|
||||||
|
*/
|
||||||
|
public static final int ENABLE = 0;
|
||||||
|
/**
|
||||||
|
* Enable check digit when optional.
|
||||||
|
*/
|
||||||
|
public static final int ADD_CHECK = 1;
|
||||||
|
/**
|
||||||
|
* Return check digit when present.
|
||||||
|
*/
|
||||||
|
public static final int EMIT_CHECK = 2;
|
||||||
|
/**
|
||||||
|
* Enable full ASCII character set.
|
||||||
|
*/
|
||||||
|
public static final int ASCII = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum data length for valid decode.
|
||||||
|
*/
|
||||||
|
public static final int MIN_LEN = 0x20;
|
||||||
|
/**
|
||||||
|
* Maximum data length for valid decode.
|
||||||
|
*/
|
||||||
|
public static final int MAX_LEN = 0x21;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required video consistency frames.
|
||||||
|
*/
|
||||||
|
public static final int UNCERTAINTY = 0x40;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable scanner to collect position data.
|
||||||
|
*/
|
||||||
|
public static final int POSITION = 0x80;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image scanner vertical scan density.
|
||||||
|
*/
|
||||||
|
public static final int X_DENSITY = 0x100;
|
||||||
|
/**
|
||||||
|
* Image scanner horizontal scan density.
|
||||||
|
*/
|
||||||
|
public static final int Y_DENSITY = 0x101;
|
||||||
|
}
|
||||||
197
src/main/java/net/sourceforge/zbar/Image.java
Normal file
197
src/main/java/net/sourceforge/zbar/Image.java
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* Image
|
||||||
|
*
|
||||||
|
* Copyright 2007-2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stores image data samples along with associated format and size
|
||||||
|
* metadata.
|
||||||
|
*/
|
||||||
|
public class Image implements Closeable {
|
||||||
|
static {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C pointer to a zbar_symbol_t.
|
||||||
|
*/
|
||||||
|
private long peer;
|
||||||
|
private Object data;
|
||||||
|
|
||||||
|
public Image() {
|
||||||
|
peer = create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image(int width, int height) {
|
||||||
|
this();
|
||||||
|
setSize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image(int width, int height, String format) {
|
||||||
|
this();
|
||||||
|
setSize(width, height);
|
||||||
|
setFormat(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image(String format) {
|
||||||
|
this();
|
||||||
|
setFormat(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
Image(long peer) {
|
||||||
|
this.peer = peer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native void init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an associated peer instance.
|
||||||
|
*/
|
||||||
|
private native long create();
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up native data associated with an instance.
|
||||||
|
*/
|
||||||
|
public synchronized void destroy() {
|
||||||
|
if(peer != 0) {
|
||||||
|
destroy(peer);
|
||||||
|
peer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the associated peer instance.
|
||||||
|
*/
|
||||||
|
private native void destroy(long peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image format conversion.
|
||||||
|
*
|
||||||
|
* @returns a @em new image with the sample data from the original
|
||||||
|
* image converted to the requested format fourcc. the original
|
||||||
|
* image is unaffected.
|
||||||
|
*/
|
||||||
|
public Image convert(String format) {
|
||||||
|
long newpeer = convert(peer, format);
|
||||||
|
if(newpeer == 0) {
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
return (new Image(newpeer));
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long convert(long peer, String format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the image format fourcc.
|
||||||
|
*/
|
||||||
|
public native String getFormat();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the fourcc image format code for image sample data.
|
||||||
|
*/
|
||||||
|
public native void setFormat(String format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a "sequence" (page/frame) number associated with this
|
||||||
|
* image.
|
||||||
|
*/
|
||||||
|
public native int getSequence();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associate a "sequence" (page/frame) number with this image.
|
||||||
|
*/
|
||||||
|
public native void setSequence(int seq);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the width of the image.
|
||||||
|
*/
|
||||||
|
public native int getWidth();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the height of the image.
|
||||||
|
*/
|
||||||
|
public native int getHeight();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the size of the image.
|
||||||
|
*/
|
||||||
|
public native int[] getSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the pixel size of the image.
|
||||||
|
*/
|
||||||
|
public native void setSize(int[] size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the pixel size of the image.
|
||||||
|
*/
|
||||||
|
public native void setSize(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the crop region of the image.
|
||||||
|
*/
|
||||||
|
public native int[] getCrop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the crop region of the image.
|
||||||
|
*/
|
||||||
|
public native void setCrop(int[] crop);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify the crop region of the image.
|
||||||
|
*/
|
||||||
|
public native void setCrop(int x, int y, int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the image sample data.
|
||||||
|
*/
|
||||||
|
public native byte[] getData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify image sample data.
|
||||||
|
*/
|
||||||
|
public native void setData(byte[] data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify image sample data.
|
||||||
|
*/
|
||||||
|
public native void setData(int[] data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the decoded results associated with this image.
|
||||||
|
*/
|
||||||
|
public SymbolSet getSymbols() {
|
||||||
|
return (new SymbolSet(getSymbols(peer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long getSymbols(long peer);
|
||||||
|
|
||||||
|
}
|
||||||
110
src/main/java/net/sourceforge/zbar/ImageScanner.java
Normal file
110
src/main/java/net/sourceforge/zbar/ImageScanner.java
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* ImageScanner
|
||||||
|
*
|
||||||
|
* Copyright 2007-2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read barcodes from 2-D images.
|
||||||
|
*/
|
||||||
|
public class ImageScanner implements Closeable {
|
||||||
|
static {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C pointer to a zbar_image_scanner_t.
|
||||||
|
*/
|
||||||
|
private long peer;
|
||||||
|
|
||||||
|
public ImageScanner() {
|
||||||
|
peer = create();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native void init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an associated peer instance.
|
||||||
|
*/
|
||||||
|
private native long create();
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up native data associated with an instance.
|
||||||
|
*/
|
||||||
|
public synchronized void destroy() {
|
||||||
|
if(peer != 0) {
|
||||||
|
destroy(peer);
|
||||||
|
peer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the associated peer instance.
|
||||||
|
*/
|
||||||
|
private native void destroy(long peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set config for indicated symbology (0 for all) to specified value.
|
||||||
|
*/
|
||||||
|
public native void setConfig(int symbology, int config, int value) throws IllegalArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse configuration string and apply to image scanner.
|
||||||
|
*/
|
||||||
|
public native void parseConfig(String config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or disable the inter-image result cache (default disabled).
|
||||||
|
* Mostly useful for scanning video frames, the cache filters duplicate
|
||||||
|
* results from consecutive images, while adding some consistency
|
||||||
|
* checking and hysteresis to the results. Invoking this method also
|
||||||
|
* clears the cache.
|
||||||
|
*/
|
||||||
|
public native void enableCache(boolean enable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve decode results for last scanned image.
|
||||||
|
*
|
||||||
|
* @returns the SymbolSet result container
|
||||||
|
*/
|
||||||
|
public SymbolSet getResults() {
|
||||||
|
return (new SymbolSet(getResults(peer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long getResults(long peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan for symbols in provided Image.
|
||||||
|
* The image format must currently be "Y800" or "GRAY".
|
||||||
|
*
|
||||||
|
* @returns the number of symbols successfully decoded from the image.
|
||||||
|
*/
|
||||||
|
public native int scanImage(Image image);
|
||||||
|
}
|
||||||
44
src/main/java/net/sourceforge/zbar/Modifier.java
Normal file
44
src/main/java/net/sourceforge/zbar/Modifier.java
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* Modifier
|
||||||
|
*
|
||||||
|
* Copyright 2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoder symbology modifiers.
|
||||||
|
*/
|
||||||
|
public class Modifier {
|
||||||
|
/**
|
||||||
|
* barcode tagged as GS1 (EAN.UCC) reserved
|
||||||
|
* (eg, FNC1 before first data character).
|
||||||
|
* data may be parsed as a sequence of GS1 AIs
|
||||||
|
*/
|
||||||
|
public static final int GS1 = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* barcode tagged as AIM reserved
|
||||||
|
* (eg, FNC1 after first character or digit pair)
|
||||||
|
*/
|
||||||
|
public static final int AIM = 1;
|
||||||
|
}
|
||||||
52
src/main/java/net/sourceforge/zbar/Orientation.java
Normal file
52
src/main/java/net/sourceforge/zbar/Orientation.java
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* Orientation
|
||||||
|
*
|
||||||
|
* Copyright 2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decoded symbol coarse orientation.
|
||||||
|
*/
|
||||||
|
public class Orientation {
|
||||||
|
/**
|
||||||
|
* Unable to determine orientation.
|
||||||
|
*/
|
||||||
|
public static final int UNKNOWN = -1;
|
||||||
|
/**
|
||||||
|
* Upright, read left to right.
|
||||||
|
*/
|
||||||
|
public static final int UP = 0;
|
||||||
|
/**
|
||||||
|
* sideways, read top to bottom
|
||||||
|
*/
|
||||||
|
public static final int RIGHT = 1;
|
||||||
|
/**
|
||||||
|
* upside-down, read right to left
|
||||||
|
*/
|
||||||
|
public static final int DOWN = 2;
|
||||||
|
/**
|
||||||
|
* sideways, read bottom to top
|
||||||
|
*/
|
||||||
|
public static final int LEFT = 3;
|
||||||
|
}
|
||||||
265
src/main/java/net/sourceforge/zbar/Symbol.java
Normal file
265
src/main/java/net/sourceforge/zbar/Symbol.java
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* Symbol
|
||||||
|
*
|
||||||
|
* Copyright 2007-2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable container for decoded result symbols associated with an image
|
||||||
|
* or a composite symbol.
|
||||||
|
*/
|
||||||
|
public class Symbol implements Closeable {
|
||||||
|
/**
|
||||||
|
* No symbol decoded.
|
||||||
|
*/
|
||||||
|
public static final int NONE = 0;
|
||||||
|
/**
|
||||||
|
* Symbol detected but not decoded.
|
||||||
|
*/
|
||||||
|
public static final int PARTIAL = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EAN-8.
|
||||||
|
*/
|
||||||
|
public static final int EAN8 = 8;
|
||||||
|
/**
|
||||||
|
* UPC-E.
|
||||||
|
*/
|
||||||
|
public static final int UPCE = 9;
|
||||||
|
/**
|
||||||
|
* ISBN-10 (from EAN-13).
|
||||||
|
*/
|
||||||
|
public static final int ISBN10 = 10;
|
||||||
|
/**
|
||||||
|
* UPC-A.
|
||||||
|
*/
|
||||||
|
public static final int UPCA = 12;
|
||||||
|
/**
|
||||||
|
* EAN-13.
|
||||||
|
*/
|
||||||
|
public static final int EAN13 = 13;
|
||||||
|
/**
|
||||||
|
* ISBN-13 (from EAN-13).
|
||||||
|
*/
|
||||||
|
public static final int ISBN13 = 14;
|
||||||
|
/**
|
||||||
|
* Interleaved 2 of 5.
|
||||||
|
*/
|
||||||
|
public static final int I25 = 25;
|
||||||
|
/**
|
||||||
|
* DataBar (RSS-14).
|
||||||
|
*/
|
||||||
|
public static final int DATABAR = 34;
|
||||||
|
/**
|
||||||
|
* DataBar Expanded.
|
||||||
|
*/
|
||||||
|
public static final int DATABAR_EXP = 35;
|
||||||
|
/**
|
||||||
|
* Codabar.
|
||||||
|
*/
|
||||||
|
public static final int CODABAR = 38;
|
||||||
|
/**
|
||||||
|
* Code 39.
|
||||||
|
*/
|
||||||
|
public static final int CODE39 = 39;
|
||||||
|
/**
|
||||||
|
* PDF417.
|
||||||
|
*/
|
||||||
|
public static final int PDF417 = 57;
|
||||||
|
/**
|
||||||
|
* QR Code.
|
||||||
|
*/
|
||||||
|
public static final int QRCODE = 64;
|
||||||
|
/**
|
||||||
|
* Code 93.
|
||||||
|
*/
|
||||||
|
public static final int CODE93 = 93;
|
||||||
|
/**
|
||||||
|
* Code 128.
|
||||||
|
*/
|
||||||
|
public static final int CODE128 = 128;
|
||||||
|
|
||||||
|
static {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C pointer to a zbar_symbol_t.
|
||||||
|
*/
|
||||||
|
private long peer;
|
||||||
|
/**
|
||||||
|
* Cached attributes.
|
||||||
|
*/
|
||||||
|
private int type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Symbols are only created by other package methods.
|
||||||
|
*/
|
||||||
|
Symbol(long peer) {
|
||||||
|
this.peer = peer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native void init();
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up native data associated with an instance.
|
||||||
|
*/
|
||||||
|
public synchronized void destroy() {
|
||||||
|
if(peer != 0) {
|
||||||
|
destroy(peer);
|
||||||
|
peer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release the associated peer instance.
|
||||||
|
*/
|
||||||
|
private native void destroy(long peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve type of decoded symbol.
|
||||||
|
*/
|
||||||
|
public int getType() {
|
||||||
|
if(type == 0) {
|
||||||
|
type = getType(peer);
|
||||||
|
}
|
||||||
|
return (type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native int getType(long peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve symbology boolean configs settings used during decode.
|
||||||
|
*/
|
||||||
|
public native int getConfigMask();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve symbology characteristics detected during decode.
|
||||||
|
*/
|
||||||
|
public native int getModifierMask();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve data decoded from symbol as a String.
|
||||||
|
*/
|
||||||
|
public native String getData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve raw data bytes decoded from symbol.
|
||||||
|
*/
|
||||||
|
public native byte[] getDataBytes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a symbol confidence metric. Quality is an unscaled,
|
||||||
|
* relative quantity: larger values are better than smaller
|
||||||
|
* values, where "large" and "small" are application dependent.
|
||||||
|
*/
|
||||||
|
public native int getQuality();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve current cache count. When the cache is enabled for
|
||||||
|
* the image_scanner this provides inter-frame reliability and
|
||||||
|
* redundancy information for video streams.
|
||||||
|
*
|
||||||
|
* @returns < 0 if symbol is still uncertain
|
||||||
|
* @returns 0 if symbol is newly verified
|
||||||
|
* @returns > 0 for duplicate symbols
|
||||||
|
*/
|
||||||
|
public native int getCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an approximate, axis-aligned bounding box for the
|
||||||
|
* symbol.
|
||||||
|
*/
|
||||||
|
public int[] getBounds() {
|
||||||
|
int n = getLocationSize(peer);
|
||||||
|
if(n <= 0) {
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] bounds = new int[4];
|
||||||
|
int xmin = Integer.MAX_VALUE;
|
||||||
|
int xmax = Integer.MIN_VALUE;
|
||||||
|
int ymin = Integer.MAX_VALUE;
|
||||||
|
int ymax = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
for(int i = 0; i < n; i++) {
|
||||||
|
int x = getLocationX(peer, i);
|
||||||
|
if(xmin > x) {
|
||||||
|
xmin = x;
|
||||||
|
}
|
||||||
|
if(xmax < x) {
|
||||||
|
xmax = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
int y = getLocationY(peer, i);
|
||||||
|
if(ymin > y) {
|
||||||
|
ymin = y;
|
||||||
|
}
|
||||||
|
if(ymax < y) {
|
||||||
|
ymax = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bounds[0] = xmin;
|
||||||
|
bounds[1] = ymin;
|
||||||
|
bounds[2] = xmax - xmin;
|
||||||
|
bounds[3] = ymax - ymin;
|
||||||
|
return (bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native int getLocationSize(long peer);
|
||||||
|
|
||||||
|
private native int getLocationX(long peer, int idx);
|
||||||
|
|
||||||
|
private native int getLocationY(long peer, int idx);
|
||||||
|
|
||||||
|
public int[] getLocationPoint(int idx) {
|
||||||
|
int[] p = new int[2];
|
||||||
|
p[0] = getLocationX(peer, idx);
|
||||||
|
p[1] = getLocationY(peer, idx);
|
||||||
|
return (p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve general axis-aligned, orientation of decoded
|
||||||
|
* symbol.
|
||||||
|
*/
|
||||||
|
public native int getOrientation();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve components of a composite result.
|
||||||
|
*/
|
||||||
|
public SymbolSet getComponents() {
|
||||||
|
return (new SymbolSet(getComponents(peer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private native long getComponents(long peer);
|
||||||
|
|
||||||
|
native long next();
|
||||||
|
}
|
||||||
75
src/main/java/net/sourceforge/zbar/SymbolIterator.java
Normal file
75
src/main/java/net/sourceforge/zbar/SymbolIterator.java
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* SymbolIterator
|
||||||
|
*
|
||||||
|
* Copyright 2007-2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterator over a SymbolSet.
|
||||||
|
*/
|
||||||
|
public class SymbolIterator implements java.util.Iterator<Symbol> {
|
||||||
|
/**
|
||||||
|
* Next symbol to be returned by the iterator.
|
||||||
|
*/
|
||||||
|
private Symbol current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SymbolIterators are only created by internal interface methods.
|
||||||
|
*/
|
||||||
|
SymbolIterator(Symbol first) {
|
||||||
|
current = first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the iteration has more elements.
|
||||||
|
*/
|
||||||
|
public boolean hasNext() {
|
||||||
|
return (current != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the next element in the iteration.
|
||||||
|
*/
|
||||||
|
public Symbol next() {
|
||||||
|
if(current == null) {
|
||||||
|
throw (new java.util.NoSuchElementException("access past end of SymbolIterator"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Symbol result = current;
|
||||||
|
long sym = current.next();
|
||||||
|
if(sym != 0) {
|
||||||
|
current = new Symbol(sym);
|
||||||
|
} else {
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raises UnsupportedOperationException.
|
||||||
|
*/
|
||||||
|
public void remove() {
|
||||||
|
throw (new UnsupportedOperationException("SymbolIterator is immutable"));
|
||||||
|
}
|
||||||
|
}
|
||||||
93
src/main/java/net/sourceforge/zbar/SymbolSet.java
Normal file
93
src/main/java/net/sourceforge/zbar/SymbolSet.java
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*------------------------------------------------------------------------
|
||||||
|
* SymbolSet
|
||||||
|
*
|
||||||
|
* Copyright 2007-2010 (c) Jeff Brown <spadix@users.sourceforge.net>
|
||||||
|
*
|
||||||
|
* This file is part of the ZBar Bar Code Reader.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is free software; you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Lesser Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2.1 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* The ZBar Bar Code Reader is distributed in the hope that it will be
|
||||||
|
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser Public License
|
||||||
|
* along with the ZBar Bar Code Reader; if not, write to the Free
|
||||||
|
* Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||||
|
* Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
* http://sourceforge.net/projects/zbar
|
||||||
|
*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable container for decoded result symbols associated with an image
|
||||||
|
* or a composite symbol.
|
||||||
|
*/
|
||||||
|
public class SymbolSet extends java.util.AbstractCollection<Symbol> implements Closeable {
|
||||||
|
static {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C pointer to a zbar_symbol_set_t.
|
||||||
|
*/
|
||||||
|
private long peer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SymbolSets are only created by other package methods.
|
||||||
|
*/
|
||||||
|
SymbolSet(long peer) {
|
||||||
|
this.peer = peer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native void init();
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up native data associated with an instance.
|
||||||
|
*/
|
||||||
|
public synchronized void destroy() {
|
||||||
|
if(peer != 0) {
|
||||||
|
destroy(peer);
|
||||||
|
peer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release the associated peer instance.
|
||||||
|
*/
|
||||||
|
private native void destroy(long peer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an iterator over the Symbol elements in this collection.
|
||||||
|
*/
|
||||||
|
public java.util.Iterator<Symbol> iterator() {
|
||||||
|
long sym = firstSymbol(peer);
|
||||||
|
if(sym == 0) {
|
||||||
|
return (new SymbolIterator(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (new SymbolIterator(new Symbol(sym)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the number of elements in the collection.
|
||||||
|
*/
|
||||||
|
public native int size();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve C pointer to first symbol in the set.
|
||||||
|
*/
|
||||||
|
private native long firstSymbol(long peer);
|
||||||
|
}
|
||||||
136
src/main/java/net/sourceforge/zbar/ZBar.java
Normal file
136
src/main/java/net/sourceforge/zbar/ZBar.java
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
package net.sourceforge.zbar;
|
||||||
|
|
||||||
|
import com.sparrowwallet.sparrow.net.NativeUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.DataBufferByte;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
public class ZBar {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ZBar.class);
|
||||||
|
|
||||||
|
private final static boolean enabled;
|
||||||
|
|
||||||
|
static { // static initializer
|
||||||
|
if(com.sparrowwallet.sparrow.io.Config.get().isUseZbar()) {
|
||||||
|
enabled = loadLibrary();
|
||||||
|
} else {
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Scan scan(BufferedImage bufferedImage) {
|
||||||
|
try {
|
||||||
|
BufferedImage grayscale = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
|
||||||
|
Graphics2D g2d = (Graphics2D)grayscale.getGraphics();
|
||||||
|
g2d.drawImage(bufferedImage, 0, 0, null);
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
byte[] data = convertToY800(grayscale);
|
||||||
|
|
||||||
|
try(Image image = new Image()) {
|
||||||
|
image.setSize(grayscale.getWidth(), grayscale.getHeight());
|
||||||
|
image.setFormat("Y800");
|
||||||
|
image.setData(data);
|
||||||
|
|
||||||
|
try(ImageScanner scanner = new ImageScanner()) {
|
||||||
|
scanner.setConfig(Symbol.NONE, Config.ENABLE, 0);
|
||||||
|
scanner.setConfig(Symbol.QRCODE, Config.ENABLE, 1);
|
||||||
|
int result = scanner.scanImage(image);
|
||||||
|
if(result != 0) {
|
||||||
|
try(SymbolSet results = scanner.getResults()) {
|
||||||
|
Scan scan = null;
|
||||||
|
for(Iterator<Symbol> iter = results.iterator(); iter.hasNext(); ) {
|
||||||
|
try(Symbol symbol = iter.next()) {
|
||||||
|
scan = new Scan(getRawBytes(symbol.getData()), symbol.getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
log.debug("Error scanning with ZBar", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] convertToY800(BufferedImage image) {
|
||||||
|
// Ensure the image is grayscale
|
||||||
|
if (image.getType() != BufferedImage.TYPE_BYTE_GRAY) {
|
||||||
|
throw new IllegalArgumentException("Input image must be grayscale");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the underlying byte array of the image data
|
||||||
|
byte[] imageData = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
|
||||||
|
|
||||||
|
// Check if the image size is even
|
||||||
|
int width = image.getWidth();
|
||||||
|
int height = image.getHeight();
|
||||||
|
if (width % 2 != 0 || height % 2 != 0) {
|
||||||
|
throw new IllegalArgumentException("Image dimensions must be even");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the output byte array in Y800 format
|
||||||
|
byte[] outputData = new byte[width * height];
|
||||||
|
int outputIndex = 0;
|
||||||
|
|
||||||
|
// Convert the grayscale image data to Y800 format
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
int pixel = imageData[y * width + x] & 0xFF; // Extract the grayscale value
|
||||||
|
|
||||||
|
// Write the grayscale value to the output byte array
|
||||||
|
outputData[outputIndex++] = (byte) pixel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean loadLibrary() {
|
||||||
|
try {
|
||||||
|
String osName = System.getProperty("os.name");
|
||||||
|
String osArch = System.getProperty("os.arch");
|
||||||
|
if(osName.startsWith("Mac") && osArch.equals("aarch64")) {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/osx/aarch64/libzbar.dylib");
|
||||||
|
} else if(osName.startsWith("Mac")) {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/osx/x64/libzbar.dylib");
|
||||||
|
} else if(osName.startsWith("Windows")) {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/windows/x64/iconv-2.dll");
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/windows/x64/zbar.dll");
|
||||||
|
} else if(osArch.equals("aarch64")) {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/linux/aarch64/libzbar.so");
|
||||||
|
} else {
|
||||||
|
NativeUtils.loadLibraryFromJar("/native/linux/x64/libzbar.so");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch(Exception e) {
|
||||||
|
log.warn("Could not load ZBar native libraries, disabling. " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] getRawBytes(String str) {
|
||||||
|
char[] chars = str.toCharArray();
|
||||||
|
byte[] bytes = new byte[chars.length];
|
||||||
|
for(int i = 0; i < chars.length; i++) {
|
||||||
|
bytes[i] = (byte)(chars[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Scan(byte[] rawData, String stringData) {}
|
||||||
|
}
|
||||||
|
|
@ -49,6 +49,7 @@
|
||||||
<SeparatorMenuItem styleClass="osxHide" />
|
<SeparatorMenuItem styleClass="osxHide" />
|
||||||
<MenuItem styleClass="osxHide" mnemonicParsing="false" text="Preferences..." accelerator="Shortcut+P" onAction="#openPreferences"/>
|
<MenuItem styleClass="osxHide" mnemonicParsing="false" text="Preferences..." accelerator="Shortcut+P" onAction="#openPreferences"/>
|
||||||
<SeparatorMenuItem />
|
<SeparatorMenuItem />
|
||||||
|
<MenuItem fx:id="renameWallet" mnemonicParsing="false" text="Rename Wallet..." onAction="#renameWallet"/>
|
||||||
<MenuItem fx:id="deleteWallet" mnemonicParsing="false" text="Delete Wallet..." onAction="#deleteWallet"/>
|
<MenuItem fx:id="deleteWallet" mnemonicParsing="false" text="Delete Wallet..." onAction="#deleteWallet"/>
|
||||||
<MenuItem fx:id="closeTab" mnemonicParsing="false" text="Close Tab" accelerator="Shortcut+W" onAction="#closeTab"/>
|
<MenuItem fx:id="closeTab" mnemonicParsing="false" text="Close Tab" accelerator="Shortcut+W" onAction="#closeTab"/>
|
||||||
<MenuItem styleClass="osxHide" mnemonicParsing="false" text="Quit" accelerator="Shortcut+Q" onAction="#quit"/>
|
<MenuItem styleClass="osxHide" mnemonicParsing="false" text="Quit" accelerator="Shortcut+Q" onAction="#quit"/>
|
||||||
|
|
@ -123,6 +124,7 @@
|
||||||
<MenuItem fx:id="lockWallet" mnemonicParsing="false" text="Lock Wallet" accelerator="Shortcut+L" onAction="#lockWallet"/>
|
<MenuItem fx:id="lockWallet" mnemonicParsing="false" text="Lock Wallet" accelerator="Shortcut+L" onAction="#lockWallet"/>
|
||||||
<MenuItem fx:id="lockAllWallets" mnemonicParsing="false" text="Lock All Wallets" accelerator="Shortcut+Shift+L" onAction="#lockWallets"/>
|
<MenuItem fx:id="lockAllWallets" mnemonicParsing="false" text="Lock All Wallets" accelerator="Shortcut+Shift+L" onAction="#lockWallets"/>
|
||||||
<SeparatorMenuItem />
|
<SeparatorMenuItem />
|
||||||
|
<MenuItem fx:id="showWalletSummary" mnemonicParsing="false" text="Show Wallet Summary" onAction="#showWalletSummary"/>
|
||||||
<MenuItem fx:id="searchWallet" mnemonicParsing="false" text="Search Wallet" accelerator="Shortcut+Shift+S" onAction="#searchWallet"/>
|
<MenuItem fx:id="searchWallet" mnemonicParsing="false" text="Search Wallet" accelerator="Shortcut+Shift+S" onAction="#searchWallet"/>
|
||||||
<MenuItem fx:id="refreshWallet" mnemonicParsing="false" text="Refresh Wallet" accelerator="Shortcut+R" onAction="#refreshWallet"/>
|
<MenuItem fx:id="refreshWallet" mnemonicParsing="false" text="Refresh Wallet" accelerator="Shortcut+R" onAction="#refreshWallet"/>
|
||||||
</items>
|
</items>
|
||||||
|
|
|
||||||
|
|
@ -320,3 +320,7 @@ CellView > .text-input.text-field {
|
||||||
-fx-background-color: -fx-control-inner-background;
|
-fx-background-color: -fx-control-inner-background;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.field-warning {
|
||||||
|
-fx-text-fill: rgb(238, 210, 2);
|
||||||
|
-fx-padding: 0 0 0 12;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
<?import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch?>
|
<?import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch?>
|
||||||
<?import com.sparrowwallet.sparrow.control.HelpLabel?>
|
<?import com.sparrowwallet.sparrow.control.HelpLabel?>
|
||||||
<?import com.sparrowwallet.sparrow.net.FeeRatesSource?>
|
<?import com.sparrowwallet.sparrow.net.FeeRatesSource?>
|
||||||
|
<?import org.controlsfx.glyphfont.Glyph?>
|
||||||
|
|
||||||
<GridPane hgap="10.0" vgap="10.0" stylesheets="@preferences.css, @../general.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.preferences.GeneralPreferencesController">
|
<GridPane hgap="10.0" vgap="10.0" stylesheets="@preferences.css, @../general.css" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sparrowwallet.sparrow.preferences.GeneralPreferencesController">
|
||||||
<padding>
|
<padding>
|
||||||
|
|
@ -60,6 +61,11 @@
|
||||||
</FXCollections>
|
</FXCollections>
|
||||||
</items>
|
</items>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
|
<Label fx:id="currenciesLoadWarning" text="Error retrieving currencies">
|
||||||
|
<graphic>
|
||||||
|
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="EXCLAMATION_TRIANGLE" styleClass="field-warning" />
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
</Field>
|
</Field>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
<Fieldset inputGrow="SOMETIMES" text="Wallet" styleClass="wideLabelFieldSet">
|
<Fieldset inputGrow="SOMETIMES" text="Wallet" styleClass="wideLabelFieldSet">
|
||||||
|
|
|
||||||
|
|
@ -287,6 +287,9 @@
|
||||||
<graphic>
|
<graphic>
|
||||||
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="ARROW_DOWN" />
|
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="ARROW_DOWN" />
|
||||||
</graphic>
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="Connect to a server (bottom right toggle) to broadcast a transaction" />
|
||||||
|
</tooltip>
|
||||||
</Button>
|
</Button>
|
||||||
<Button fx:id="payjoinButton" defaultButton="true" HBox.hgrow="ALWAYS" text="Get Payjoin Transaction" contentDisplay="TOP" wrapText="true" textAlignment="CENTER" onAction="#getPayjoinTransaction">
|
<Button fx:id="payjoinButton" defaultButton="true" HBox.hgrow="ALWAYS" text="Get Payjoin Transaction" contentDisplay="TOP" wrapText="true" textAlignment="CENTER" onAction="#getPayjoinTransaction">
|
||||||
<graphic>
|
<graphic>
|
||||||
|
|
|
||||||
|
|
@ -140,4 +140,12 @@
|
||||||
.address-text-field {
|
.address-text-field {
|
||||||
-fx-font-size: 13px;
|
-fx-font-size: 13px;
|
||||||
-fx-font-family: 'Roboto Mono';
|
-fx-font-family: 'Roboto Mono';
|
||||||
|
}
|
||||||
|
|
||||||
|
.unconfirmed-row {
|
||||||
|
-fx-opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-row {
|
||||||
|
-fx-font-weight: bold;
|
||||||
}
|
}
|
||||||
Binary file not shown.
BIN
src/main/resources/native/linux/aarch64/libzbar.so
Executable file
BIN
src/main/resources/native/linux/aarch64/libzbar.so
Executable file
Binary file not shown.
Binary file not shown.
BIN
src/main/resources/native/linux/x64/libzbar.so
Executable file
BIN
src/main/resources/native/linux/x64/libzbar.so
Executable file
Binary file not shown.
Binary file not shown.
BIN
src/main/resources/native/osx/aarch64/libzbar.dylib
Executable file
BIN
src/main/resources/native/osx/aarch64/libzbar.dylib
Executable file
Binary file not shown.
Binary file not shown.
BIN
src/main/resources/native/osx/x64/libzbar.dylib
Executable file
BIN
src/main/resources/native/osx/x64/libzbar.dylib
Executable file
Binary file not shown.
Binary file not shown.
BIN
src/main/resources/native/windows/x64/iconv-2.dll
Normal file
BIN
src/main/resources/native/windows/x64/iconv-2.dll
Normal file
Binary file not shown.
BIN
src/main/resources/native/windows/x64/zbar.dll
Normal file
BIN
src/main/resources/native/windows/x64/zbar.dll
Normal file
Binary file not shown.
Loading…
Reference in a new issue