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'
|
||||
}
|
||||
|
||||
def sparrowVersion = '1.7.8'
|
||||
def sparrowVersion = '1.7.10'
|
||||
def os = org.gradle.internal.os.OperatingSystem.current()
|
||||
def osName = os.getFamilyName()
|
||||
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:
|
||||
|
||||
```shell
|
||||
GIT_TAG="1.7.7"
|
||||
GIT_TAG="1.7.9"
|
||||
```
|
||||
|
||||
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>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.7.8</string>
|
||||
<string>1.7.10</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
|
||||
|
|
|
|||
|
|
@ -166,6 +166,9 @@ public class AppController implements Initializable {
|
|||
@FXML
|
||||
private MenuItem lockAllWallets;
|
||||
|
||||
@FXML
|
||||
private MenuItem showWalletSummary;
|
||||
|
||||
@FXML
|
||||
private MenuItem searchWallet;
|
||||
|
||||
|
|
@ -210,6 +213,8 @@ public class AppController implements Initializable {
|
|||
|
||||
private Timeline statusTimeline;
|
||||
|
||||
private SendToManyDialog sendToManyDialog;
|
||||
|
||||
private Tab previouslySelectedTab;
|
||||
|
||||
private boolean subTabsVisible;
|
||||
|
|
@ -218,6 +223,8 @@ public class AppController implements Initializable {
|
|||
|
||||
private final Set<Wallet> emptyLoadingWallets = new LinkedHashSet<>();
|
||||
|
||||
private final Map<File, File> renamedWallets = new HashMap<>();
|
||||
|
||||
private final ChangeListener<Boolean> serverToggleOnlineListener = (observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> setServerToggleTooltip(getCurrentBlockHeight()));
|
||||
};
|
||||
|
|
@ -376,6 +383,7 @@ public class AppController implements Initializable {
|
|||
deleteWallet.disableProperty().bind(exportWallet.disableProperty());
|
||||
closeTab.setDisable(true);
|
||||
lockWallet.setDisable(true);
|
||||
showWalletSummary.disableProperty().bind(exportWallet.disableProperty());
|
||||
searchWallet.disableProperty().bind(exportWallet.disableProperty());
|
||||
refreshWallet.disableProperty().bind(Bindings.or(exportWallet.disableProperty(), Bindings.or(serverToggle.disableProperty(), AppServices.onlineProperty().not())));
|
||||
sendToMany.disableProperty().bind(exportWallet.disableProperty());
|
||||
|
|
@ -467,7 +475,17 @@ public class AppController implements Initializable {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
@ -839,6 +857,10 @@ public class AppController implements Initializable {
|
|||
}
|
||||
}
|
||||
|
||||
public void renameWallet(ActionEvent event) {
|
||||
renameWallet(getSelectedWalletForm());
|
||||
}
|
||||
|
||||
public void deleteWallet(ActionEvent event) {
|
||||
deleteWallet(getSelectedWalletForm());
|
||||
}
|
||||
|
|
@ -1286,6 +1308,13 @@ public class AppController implements Initializable {
|
|||
}
|
||||
|
||||
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();
|
||||
if(selectedWalletForm != null) {
|
||||
Wallet wallet = selectedWalletForm.getWallet();
|
||||
|
|
@ -1294,8 +1323,10 @@ public class AppController implements Initializable {
|
|||
bitcoinUnit = wallet.getAutoUnit();
|
||||
}
|
||||
|
||||
SendToManyDialog sendToManyDialog = new SendToManyDialog(bitcoinUnit);
|
||||
sendToManyDialog = new SendToManyDialog(bitcoinUnit);
|
||||
sendToManyDialog.initModality(Modality.NONE);
|
||||
Optional<List<Payment>> optPayments = sendToManyDialog.showAndWait();
|
||||
sendToManyDialog = null;
|
||||
optPayments.ifPresent(payments -> {
|
||||
if(!payments.isEmpty()) {
|
||||
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) {
|
||||
WalletForm selectedWalletForm = getSelectedWalletForm();
|
||||
if(selectedWalletForm != null) {
|
||||
|
|
@ -1527,6 +1573,11 @@ public class AppController implements Initializable {
|
|||
|
||||
tabs.getTabs().add(tab);
|
||||
tabs.getSelectionModel().select(tab);
|
||||
|
||||
File oldWalletFile = renamedWallets.remove(storage.getWalletFile());
|
||||
if(oldWalletFile != null) {
|
||||
deleteStorage(new Storage(oldWalletFile), false);
|
||||
}
|
||||
} else {
|
||||
for(Tab walletTab : tabs.getTabs()) {
|
||||
TabData tabData = (TabData)walletTab.getUserData();
|
||||
|
|
@ -1603,6 +1654,9 @@ public class AppController implements Initializable {
|
|||
subTabLabel.setGraphic(getSubTabGlyph(wallet));
|
||||
subTabLabel.setContentDisplay(ContentDisplay.TOP);
|
||||
subTabLabel.setAlignment(Pos.TOP_CENTER);
|
||||
if(isSubTabLabelTruncated(subTabLabel, label)) {
|
||||
subTabLabel.setTooltip(new Tooltip(label));
|
||||
}
|
||||
subTab.setGraphic(subTabLabel);
|
||||
FXMLLoader walletLoader = new FXMLLoader(getClass().getResource("wallet/wallet.fxml"));
|
||||
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) {
|
||||
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) {
|
||||
|
|
@ -1964,7 +2042,7 @@ public class AppController implements Initializable {
|
|||
|
||||
try {
|
||||
tabs.getTabs().remove(tabs.getSelectionModel().getSelectedItem());
|
||||
deleteStorage(storage);
|
||||
deleteStorage(storage, true);
|
||||
} finally {
|
||||
encryptionFullKey.clear();
|
||||
password.get().clear();
|
||||
|
|
@ -1986,15 +2064,15 @@ public class AppController implements Initializable {
|
|||
}
|
||||
} else {
|
||||
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()) {
|
||||
Platform.runLater(() -> {
|
||||
Storage.DeleteWalletService deleteWalletService = new Storage.DeleteWalletService(storage);
|
||||
Storage.DeleteWalletService deleteWalletService = new Storage.DeleteWalletService(storage, deleteBackups);
|
||||
deleteWalletService.setDelay(Duration.seconds(3));
|
||||
deleteWalletService.setPeriod(Duration.hours(1));
|
||||
deleteWalletService.setOnSucceeded(event -> {
|
||||
|
|
@ -2010,7 +2088,7 @@ public class AppController implements Initializable {
|
|||
deleteWalletService.start();
|
||||
});
|
||||
} else {
|
||||
Platform.runLater(() -> deleteStorage(storage));
|
||||
Platform.runLater(() -> deleteStorage(storage, deleteBackups));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2026,6 +2104,11 @@ public class AppController implements Initializable {
|
|||
if(optLabel.isPresent()) {
|
||||
String label = optLabel.get();
|
||||
subTabLabel.setText(label);
|
||||
if(isSubTabLabelTruncated(subTabLabel, label)) {
|
||||
subTabLabel.setTooltip(new Tooltip(label));
|
||||
} else {
|
||||
subTabLabel.setTooltip(null);
|
||||
}
|
||||
|
||||
Wallet renamedWallet = AppServices.get().getWallet(walletId);
|
||||
renamedWallet.setLabel(label);
|
||||
|
|
@ -2050,9 +2133,18 @@ public class AppController implements Initializable {
|
|||
contextMenu.getItems().add(delete);
|
||||
}
|
||||
|
||||
contextMenu.setOnShowing(event -> {
|
||||
Wallet renameWallet = AppServices.get().getWallet(walletId);
|
||||
rename.setDisable(!renameWallet.isValid());
|
||||
});
|
||||
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
private boolean isSubTabLabelTruncated(Label subTabLabel, String label) {
|
||||
return TextUtils.computeTextWidth(subTabLabel.getFont(), label, 0.0D) > (90-6);
|
||||
}
|
||||
|
||||
private void configureSwitchServer() {
|
||||
switchServer.getItems().clear();
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ public class DefaultInteractionServices implements InteractionServices {
|
|||
alert.getDialogPane().setPrefHeight(200 + numLines * 20);
|
||||
}
|
||||
|
||||
alert.setResizable(true);
|
||||
|
||||
moveToActiveWindowScreen(alert);
|
||||
return alert.showAndWait();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import java.util.*;
|
|||
public class SparrowWallet {
|
||||
public static final String APP_ID = "com.sparrowwallet.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_HOME_PROPERTY = "sparrow.home";
|
||||
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.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.CurrencyRate;
|
||||
import com.sparrowwallet.sparrow.UnitFormat;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
|
|
@ -17,6 +18,7 @@ import javafx.geometry.Pos;
|
|||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TreeTableColumn;
|
||||
import javafx.scene.control.TreeTableView;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
|
|
@ -28,6 +30,7 @@ import java.util.Optional;
|
|||
public class CoinTreeTable extends TreeTableView<Entry> {
|
||||
private BitcoinUnit bitcoinUnit;
|
||||
private UnitFormat unitFormat;
|
||||
private CurrencyRate currencyRate;
|
||||
|
||||
public BitcoinUnit getBitcoinUnit() {
|
||||
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) {
|
||||
if(getRoot() != null) {
|
||||
Entry entry = getRoot().getValue();
|
||||
|
|
@ -119,4 +134,12 @@ public class CoinTreeTable extends TreeTableView<Entry> {
|
|||
stackPane.setAlignment(Pos.CENTER);
|
||||
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(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) {
|
||||
MenuItem item = new MenuItem(scriptType.getDescription());
|
||||
final List<ChildNumber> derivation = scriptType.getDefaultDerivation();
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
|||
actionBox.getChildren().add(viewTransactionButton);
|
||||
|
||||
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())) {
|
||||
Button increaseFeeButton = new Button("");
|
||||
increaseFeeButton.setGraphic(getIncreaseFeeRBFGlyph());
|
||||
|
|
@ -216,7 +216,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
|||
.map(e -> (HashIndexEntry)e)
|
||||
.filter(e -> e.getType().equals(HashIndexEntry.Type.INPUT) && e.isSpendable())
|
||||
.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())
|
||||
.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()))
|
||||
.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();
|
||||
Transaction tx = blockTransaction.getTransaction();
|
||||
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())
|
||||
.stream().filter(outputGroup -> outputGroup.getEffectiveValue() >= 0).collect(Collectors.toList());
|
||||
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
|
||||
OutputGroup outputGroup = outputGroups.remove(0);
|
||||
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)));
|
||||
}
|
||||
|
||||
private static boolean canRBF(BlockTransaction blockTransaction) {
|
||||
return Config.get().isMempoolFullRbf() || blockTransaction.getTransaction().isReplaceByFee();
|
||||
}
|
||||
|
||||
private static boolean canSignMessage(WalletNode walletNode) {
|
||||
Wallet wallet = walletNode.getWallet();
|
||||
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 += "\nRBF: " + (transactionEntry.getBlockTransaction().getTransaction().isReplaceByFee() ? "Enabled" : "Disabled");
|
||||
tooltip += "\nRBF: " + (canRBF(transactionEntry.getBlockTransaction()) ? "Enabled" : "Disabled");
|
||||
}
|
||||
|
||||
return tooltip;
|
||||
|
|
@ -546,7 +551,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
|||
});
|
||||
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)");
|
||||
increaseFee.setGraphic(getIncreaseFeeRBFGlyph());
|
||||
increaseFee.setOnAction(AE -> {
|
||||
|
|
@ -557,7 +562,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
|||
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)");
|
||||
cancelTx.setGraphic(getCancelTransactionRBFGlyph());
|
||||
cancelTx.setOnAction(AE -> {
|
||||
|
|
@ -789,6 +794,8 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
|||
cell.getStyleClass().remove("transaction-row");
|
||||
cell.getStyleClass().remove("node-row");
|
||||
cell.getStyleClass().remove("utxo-row");
|
||||
cell.getStyleClass().remove("unconfirmed-row");
|
||||
cell.getStyleClass().remove("summary-row");
|
||||
cell.getStyleClass().remove("address-cell");
|
||||
cell.getStyleClass().remove("hashindex-row");
|
||||
cell.getStyleClass().remove("confirming");
|
||||
|
|
@ -823,6 +830,10 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
|||
if(hashIndexEntry.isSpent()) {
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
verified = Bip322.verifyMessageBip322(getAddress().getScriptType(), getAddress(), message.getText().trim(), signature.getText().trim());
|
||||
if(verified) {
|
||||
|
|
|
|||
|
|
@ -502,18 +502,22 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
|||
if(cryptoOutput.getMultiKey() != null) {
|
||||
MultiKey multiKey = cryptoOutput.getMultiKey();
|
||||
Map<ExtendedKey, KeyDerivation> extendedPublicKeys = new LinkedHashMap<>();
|
||||
Map<ExtendedKey, String> extendedPublicKeyLabels = new LinkedHashMap<>();
|
||||
for(CryptoHDKey cryptoHDKey : multiKey.getHdKeys()) {
|
||||
ExtendedKey extendedKey = getExtendedKey(cryptoHDKey);
|
||||
KeyDerivation keyDerivation = getKeyDerivation(cryptoHDKey.getOrigin());
|
||||
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) {
|
||||
throw new IllegalArgumentException("EC keys are currently unsupported");
|
||||
} else if(cryptoOutput.getHdKey() != null) {
|
||||
ExtendedKey extendedKey = getExtendedKey(cryptoOutput.getHdKey());
|
||||
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");
|
||||
|
|
|
|||
|
|
@ -140,6 +140,8 @@ public class SearchWalletDialog extends Dialog<Entry> {
|
|||
|
||||
setResizable(true);
|
||||
|
||||
AppServices.moveToActiveWindowScreen(this);
|
||||
|
||||
Platform.runLater(search::requestFocus);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,8 +50,7 @@ public class TransactionsTreeTable extends CoinTreeTable {
|
|||
setPlaceholder(getDefaultPlaceholder(rootEntry.getWallet()));
|
||||
setEditable(true);
|
||||
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||
dateCol.setSortType(TreeTableColumn.SortType.DESCENDING);
|
||||
getSortOrder().add(dateCol);
|
||||
setSortColumn(0, TreeTableColumn.SortType.DESCENDING);
|
||||
}
|
||||
|
||||
public void updateAll(WalletTransactionsEntry rootEntry) {
|
||||
|
|
@ -61,16 +60,13 @@ public class TransactionsTreeTable extends CoinTreeTable {
|
|||
setRoot(rootItem);
|
||||
rootItem.setExpanded(true);
|
||||
|
||||
if(getColumns().size() > 0 && getSortOrder().isEmpty()) {
|
||||
TreeTableColumn<Entry, ?> dateCol = getColumns().get(0);
|
||||
getSortOrder().add(dateCol);
|
||||
dateCol.setSortType(TreeTableColumn.SortType.DESCENDING);
|
||||
}
|
||||
setSortColumn(0, TreeTableColumn.SortType.DESCENDING);
|
||||
}
|
||||
|
||||
public void updateHistory() {
|
||||
//Transaction entries should have already been updated using WalletTransactionsEntry.updateHistory, so only a resort required
|
||||
sort();
|
||||
setSortColumn(0, TreeTableColumn.SortType.DESCENDING);
|
||||
}
|
||||
|
||||
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()));
|
||||
setEditable(true);
|
||||
setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY);
|
||||
amountCol.setSortType(TreeTableColumn.SortType.DESCENDING);
|
||||
getSortOrder().add(amountCol);
|
||||
setSortColumn(getColumns().size() - 1, TreeTableColumn.SortType.DESCENDING);
|
||||
|
||||
getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
}
|
||||
|
|
@ -96,17 +95,14 @@ public class UtxosTreeTable extends CoinTreeTable {
|
|||
setRoot(rootItem);
|
||||
rootItem.setExpanded(true);
|
||||
|
||||
if(getColumns().size() > 0 && getSortOrder().isEmpty()) {
|
||||
TreeTableColumn<Entry, ?> amountCol = getColumns().get(getColumns().size() - 1);
|
||||
getSortOrder().add(amountCol);
|
||||
amountCol.setSortType(TreeTableColumn.SortType.DESCENDING);
|
||||
}
|
||||
setSortColumn(getColumns().size() - 1, TreeTableColumn.SortType.DESCENDING);
|
||||
}
|
||||
|
||||
public void updateHistory() {
|
||||
//Utxo entries should have already been updated, so only a resort required
|
||||
if(!getRoot().getChildren().isEmpty()) {
|
||||
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()) {
|
||||
walletImporters.add(new WalletLabels(selectedWalletForms));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,13 @@ public class WalletNameDialog extends Dialog<WalletNameDialog.NameAndBirthDate>
|
|||
}
|
||||
|
||||
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();
|
||||
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");
|
||||
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);
|
||||
Button okButton = (Button) dialogPane.lookupButton(okButtonType);
|
||||
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);
|
||||
|
||||
name.setPromptText("Wallet Name");
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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.embed.swing.SwingFXUtils;
|
||||
import javafx.scene.image.Image;
|
||||
import net.sourceforge.zbar.ZBar;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -155,6 +156,13 @@ public class WebcamService extends ScheduledService<Image> {
|
|||
LuminanceSource source = new BufferedImageLuminanceSource(bufferedImage);
|
||||
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 {
|
||||
return qrReader.decode(bitmap, Map.of(DecodeHintType.TRY_HARDER, Boolean.TRUE));
|
||||
} catch(ReaderException e) {
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ public class Config {
|
|||
private int enumerateHwPeriod = ENUMERATE_HW_PERIOD_SECS;
|
||||
private QRDensity qrDensity;
|
||||
private Boolean hdCapture;
|
||||
private boolean useZbar = true;
|
||||
private String webcamDevice;
|
||||
private ServerType serverType;
|
||||
private Server publicElectrumServer;
|
||||
|
|
@ -77,6 +78,7 @@ public class Config {
|
|||
private int maxPageSize = DEFAULT_PAGE_SIZE;
|
||||
private boolean usePayNym;
|
||||
private boolean sameAppMixing;
|
||||
private boolean mempoolFullRbf;
|
||||
private Double appWidth;
|
||||
private Double appHeight;
|
||||
|
||||
|
|
@ -404,6 +406,10 @@ public class Config {
|
|||
flush();
|
||||
}
|
||||
|
||||
public boolean isUseZbar() {
|
||||
return useZbar;
|
||||
}
|
||||
|
||||
public String getWebcamDevice() {
|
||||
return webcamDevice;
|
||||
}
|
||||
|
|
@ -665,6 +671,15 @@ public class Config {
|
|||
flush();
|
||||
}
|
||||
|
||||
public boolean isMempoolFullRbf() {
|
||||
return mempoolFullRbf;
|
||||
}
|
||||
|
||||
public void setMempoolFullRbf(boolean mempoolFullRbf) {
|
||||
this.mempoolFullRbf = mempoolFullRbf;
|
||||
flush();
|
||||
}
|
||||
|
||||
public Double getAppWidth() {
|
||||
return appWidth;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public class Hwi {
|
|||
private static final Logger log = LoggerFactory.getLogger(Hwi.class);
|
||||
private static final String HWI_HOME_DIR = "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 boolean isPromptActive = false;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package com.sparrowwallet.sparrow.io;
|
||||
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class JadeMultisig extends ColdcardMultisig {
|
||||
@Override
|
||||
public String getName() {
|
||||
|
|
@ -38,4 +41,30 @@ public class JadeMultisig extends ColdcardMultisig {
|
|||
public boolean walletExportRequiresDecryption() {
|
||||
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);
|
||||
tempStorage.setKeyDeriver(storage.getKeyDeriver());
|
||||
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);
|
||||
}
|
||||
persistence.close();
|
||||
|
|
|
|||
|
|
@ -265,8 +265,11 @@ public class Storage {
|
|||
persistence.copyWallet(walletFile, outputStream);
|
||||
}
|
||||
|
||||
public boolean delete() {
|
||||
deleteBackups();
|
||||
public boolean delete(boolean deleteBackups) {
|
||||
if(deleteBackups) {
|
||||
deleteBackups();
|
||||
}
|
||||
|
||||
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> {
|
||||
private final Storage storage;
|
||||
private final boolean deleteBackups;
|
||||
|
||||
public DeleteWalletService(Storage storage) {
|
||||
public DeleteWalletService(Storage storage, boolean deleteBackups) {
|
||||
this.storage = storage;
|
||||
this.deleteBackups = deleteBackups;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task<Boolean> createTask() {
|
||||
return new Task<>() {
|
||||
protected Boolean call() {
|
||||
return storage.delete();
|
||||
return storage.delete(deleteBackups);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,4 +8,7 @@ public interface WalletImport extends FileImport {
|
|||
String getWalletImportDescription();
|
||||
Wallet importWallet(InputStream inputStream, String password) throws ImportException;
|
||||
boolean isWalletImportScannable();
|
||||
default boolean isWalletImportFileFormatAvailable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -583,7 +583,7 @@ public class DbPersistence implements Persistence {
|
|||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return dataSource.isClosed();
|
||||
return dataSource == null || dataSource.isClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ public interface MixConfigDao {
|
|||
|
||||
default void addMixConfig(Wallet wallet) {
|
||||
if(wallet.getMixConfig() != null) {
|
||||
wallet.getMixConfig().setId(null);
|
||||
addOrUpdate(wallet, wallet.getMixConfig());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,9 +96,8 @@ public final class IpAddressMatcher {
|
|||
private InetAddress parseAddress(String address) {
|
||||
try {
|
||||
return InetAddress.getByName(address);
|
||||
}
|
||||
catch (UnknownHostException e) {
|
||||
throw new IllegalArgumentException("Failed to parse address: " + address, e);
|
||||
} catch(UnknownHostException e) {
|
||||
throw new IllegalArgumentException("Failed to resolve address: " + address, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ public enum PublicElectrumServer {
|
|||
EMZY_DE("electrum.emzy.de", "ssl://electrum.emzy.de: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),
|
||||
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);
|
||||
|
||||
PublicElectrumServer(String name, String url, Network network) {
|
||||
|
|
|
|||
|
|
@ -86,6 +86,10 @@ public class Tor {
|
|||
dormantCanceledByStartup.set(TorConfig.Option.AorTorF.getTrue());
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -141,11 +141,13 @@ public class BitcoindClient {
|
|||
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));
|
||||
|
||||
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);
|
||||
} else {
|
||||
List<String> wallets = getBitcoindService().listWallets();
|
||||
if(!wallets.contains(CORE_WALLET_NAME)) {
|
||||
if(!loaded) {
|
||||
getBitcoindService().loadWallet(CORE_WALLET_NAME, true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import javafx.collections.FXCollections;
|
|||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.util.StringConverter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -45,6 +46,9 @@ public class GeneralPreferencesController extends PreferencesDetailController {
|
|||
@FXML
|
||||
private ComboBox<ExchangeSource> exchangeSource;
|
||||
|
||||
@FXML
|
||||
private Label currenciesLoadWarning;
|
||||
|
||||
@FXML
|
||||
private UnlabeledToggleSwitch loadRecentWallets;
|
||||
|
||||
|
|
@ -87,6 +91,9 @@ public class GeneralPreferencesController extends PreferencesDetailController {
|
|||
EventManager.get().post(new FeeRatesSourceChangedEvent(newValue));
|
||||
});
|
||||
|
||||
currenciesLoadWarning.managedProperty().bind(currenciesLoadWarning.visibleProperty());
|
||||
currenciesLoadWarning.setVisible(false);
|
||||
|
||||
blockExplorers.setItems(getBlockExplorerList());
|
||||
blockExplorers.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
|
|
@ -237,6 +244,8 @@ public class GeneralPreferencesController extends PreferencesDetailController {
|
|||
fiatCurrency.setDisable(true);
|
||||
}
|
||||
|
||||
currenciesLoadWarning.setVisible(exchangeSource.getValue() != ExchangeSource.NONE && currencies.isEmpty());
|
||||
|
||||
//Always fire event regardless of previous selection to update rates
|
||||
EventManager.get().post(new FiatCurrencySelectedEvent(exchangeSource.getValue(), fiatCurrency.getValue()));
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import java.util.stream.Collectors;
|
|||
public class ServerAliasDialog extends Dialog<Server> {
|
||||
private final ServerType serverType;
|
||||
private final TableView<ServerEntry> serverTable;
|
||||
private final Button closeButton;
|
||||
|
||||
public ServerAliasDialog(ServerType serverType) {
|
||||
this.serverType = serverType;
|
||||
|
|
@ -76,6 +77,7 @@ public class ServerAliasDialog extends Dialog<Server> {
|
|||
|
||||
Button selectButton = (Button)dialogPane.lookupButton(selectButtonType);
|
||||
Button deleteButton = (Button)dialogPane.lookupButton(deleteButtonType);
|
||||
closeButton = (Button)dialogPane.lookupButton(ButtonType.CLOSE);
|
||||
selectButton.setDefaultButton(true);
|
||||
selectButton.setDisable(true);
|
||||
deleteButton.setDisable(true);
|
||||
|
|
@ -112,8 +114,14 @@ public class ServerAliasDialog extends Dialog<Server> {
|
|||
serverTable.getItems().remove(serverEntry);
|
||||
if(serverType == ServerType.BITCOIN_CORE) {
|
||||
Config.get().removeRecentCoreServer(serverEntry.getServer());
|
||||
if(serverEntry.getServer().equals(Config.get().getCoreServer()) && !serverTable.getItems().isEmpty()) {
|
||||
closeButton.setDisable(true);
|
||||
}
|
||||
} else {
|
||||
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.addListener((selectedIndex, previousSelection, changedByUserInteraction) -> {
|
||||
if(selectedIndex != previousSelection) {
|
||||
Config.get().setPublicElectrumServer(PublicElectrumServer.values()[selectedIndex].getServer());
|
||||
Config.get().setPublicElectrumServer(PublicElectrumServer.getServers().get(selectedIndex).getServer());
|
||||
}
|
||||
});
|
||||
mainPanel.addComponent(url);
|
||||
|
|
|
|||
|
|
@ -192,6 +192,9 @@ public class UtxosDialog extends WalletDialog {
|
|||
SparrowTerminal.get().getGuiThread().invokeLater(() -> {
|
||||
TableModel<TableCell> tableModel = getTableModel(walletUtxosEntry);
|
||||
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(
|
||||
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 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(
|
||||
|
|
@ -551,6 +551,9 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
fingerprint.setText(keyDerivation.getMasterFingerprint());
|
||||
derivation.setText(keyDerivation.getDerivationPath());
|
||||
xpub.setText(extendedKey.toString());
|
||||
if(result.outputDescriptor.getExtendedPublicKeyLabel(extendedKey) != null) {
|
||||
label.setText(result.outputDescriptor.getExtendedPublicKeyLabel(extendedKey));
|
||||
}
|
||||
} else if(result.wallets != null) {
|
||||
for(Wallet wallet : result.wallets) {
|
||||
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());
|
||||
derivation.setText(keystore.getKeyDerivation().getDerivationPath());
|
||||
xpub.setText(keystore.getExtendedPublicKey().toString());
|
||||
if(!Keystore.DEFAULT_LABEL.equals(keystore.getLabel())) {
|
||||
label.setText(keystore.getLabel());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import java.util.*;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||
import static com.sparrowwallet.sparrow.AppServices.showWarningDialog;
|
||||
|
||||
public class SettingsController extends WalletFormController implements Initializable {
|
||||
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 boolean initialising = true;
|
||||
private boolean reverting;
|
||||
|
||||
@Override
|
||||
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) -> {
|
||||
if(scriptType != null) {
|
||||
walletForm.getWallet().setScriptType(scriptType);
|
||||
scriptType.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if(newValue != null) {
|
||||
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));
|
||||
|
|
@ -215,7 +240,10 @@ public class SettingsController extends WalletFormController implements Initiali
|
|||
totalKeystores.setValue(0);
|
||||
walletForm.revert();
|
||||
initialising = true;
|
||||
reverting = true;
|
||||
setFieldsFromWallet(walletForm.getWallet());
|
||||
reverting = false;
|
||||
initialising = false;
|
||||
});
|
||||
|
||||
apply.setOnAction(event -> {
|
||||
|
|
@ -313,12 +341,12 @@ public class SettingsController extends WalletFormController implements Initiali
|
|||
if(optionalResult.isPresent()) {
|
||||
QRScanDialog.Result result = optionalResult.get();
|
||||
if(result.outputDescriptor != null) {
|
||||
setDescriptorText(result.outputDescriptor.toString());
|
||||
replaceWallet(result.outputDescriptor.toWallet());
|
||||
} else if(result.wallets != null) {
|
||||
for(Wallet wallet : result.wallets) {
|
||||
if(scriptType.getValue().equals(wallet.getScriptType()) && !wallet.getKeystores().isEmpty()) {
|
||||
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(wallet);
|
||||
setDescriptorText(outputDescriptor.toString());
|
||||
replaceWallet(outputDescriptor.toWallet());
|
||||
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());
|
||||
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());
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -506,16 +506,22 @@ public class WalletForm {
|
|||
for(WalletNode childNode : wallet.getNode(keyPurpose).getChildren()) {
|
||||
for(BlockTransactionHashIndex receivedRef : childNode.getTransactionOutputs()) {
|
||||
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)"));
|
||||
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());
|
||||
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)");
|
||||
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) {
|
||||
if(event.getWallet() == 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);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +77,7 @@ public class WalletTransactionsEntry extends Entry {
|
|||
.collect(Collectors.toUnmodifiableMap(entry -> new HashIndex(entry.getKey().getHash(), entry.getKey().getIndex()), Map.Entry::getKey,
|
||||
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> 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());
|
||||
|
||||
for(KeyPurpose keyPurpose : wallet.getWalletKeyPurposes()) {
|
||||
|
|
@ -109,7 +113,7 @@ public class WalletTransactionsEntry extends Entry {
|
|||
}
|
||||
|
||||
for(Wallet childWallet : wallet.getChildWallets()) {
|
||||
if(childWallet.isNested()) {
|
||||
if(includeAllChildWallets || childWallet.isNested()) {
|
||||
for(KeyPurpose keyPurpose : childWallet.getWalletKeyPurposes()) {
|
||||
getWalletTransactions(childWallet, walletTransactionMap, childWallet.getNode(keyPurpose));
|
||||
}
|
||||
|
|
@ -218,6 +222,14 @@ public class WalletTransactionsEntry extends Entry {
|
|||
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 final Wallet wallet;
|
||||
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" />
|
||||
<MenuItem styleClass="osxHide" mnemonicParsing="false" text="Preferences..." accelerator="Shortcut+P" onAction="#openPreferences"/>
|
||||
<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="closeTab" mnemonicParsing="false" text="Close Tab" accelerator="Shortcut+W" onAction="#closeTab"/>
|
||||
<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="lockAllWallets" mnemonicParsing="false" text="Lock All Wallets" accelerator="Shortcut+Shift+L" onAction="#lockWallets"/>
|
||||
<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="refreshWallet" mnemonicParsing="false" text="Refresh Wallet" accelerator="Shortcut+R" onAction="#refreshWallet"/>
|
||||
</items>
|
||||
|
|
|
|||
|
|
@ -320,3 +320,7 @@ CellView > .text-input.text-field {
|
|||
-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.HelpLabel?>
|
||||
<?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">
|
||||
<padding>
|
||||
|
|
@ -60,6 +61,11 @@
|
|||
</FXCollections>
|
||||
</items>
|
||||
</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>
|
||||
</Fieldset>
|
||||
<Fieldset inputGrow="SOMETIMES" text="Wallet" styleClass="wideLabelFieldSet">
|
||||
|
|
|
|||
|
|
@ -287,6 +287,9 @@
|
|||
<graphic>
|
||||
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="ARROW_DOWN" />
|
||||
</graphic>
|
||||
<tooltip>
|
||||
<Tooltip text="Connect to a server (bottom right toggle) to broadcast a transaction" />
|
||||
</tooltip>
|
||||
</Button>
|
||||
<Button fx:id="payjoinButton" defaultButton="true" HBox.hgrow="ALWAYS" text="Get Payjoin Transaction" contentDisplay="TOP" wrapText="true" textAlignment="CENTER" onAction="#getPayjoinTransaction">
|
||||
<graphic>
|
||||
|
|
|
|||
|
|
@ -140,4 +140,12 @@
|
|||
.address-text-field {
|
||||
-fx-font-size: 13px;
|
||||
-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