exclude utxos from transaction diagram

This commit is contained in:
Craig Raw 2020-09-26 12:30:33 +02:00
parent 32bc9f6a99
commit eb09076604
6 changed files with 138 additions and 22 deletions

2
drongo

@ -1 +1 @@
Subproject commit b2297a8d0219af70b3e97c83feb1af8aec485d9f Subproject commit e8d8fa61268ec8ac4dd5c14e6715d4a4bde2fe49

View file

@ -4,11 +4,15 @@ import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
import com.sparrowwallet.drongo.wallet.WalletNode; import com.sparrowwallet.drongo.wallet.WalletNode;
import com.sparrowwallet.drongo.wallet.WalletTransaction; import com.sparrowwallet.drongo.wallet.WalletTransaction;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.ExcludeUtxoEvent;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Group; import javafx.scene.Group;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Tooltip; import javafx.scene.control.Tooltip;
import javafx.scene.layout.*; import javafx.scene.layout.*;
@ -146,6 +150,16 @@ public class TransactionDiagram extends GridPane {
WalletNode walletNode = displayedUtxos.get(input); WalletNode walletNode = displayedUtxos.get(input);
String desc = getInputDescription(input); String desc = getInputDescription(input);
Label label = new Label(desc); Label label = new Label(desc);
label.getStyleClass().add("utxo-label");
Button excludeUtxoButton = new Button("");
excludeUtxoButton.setGraphic(getExcludeGlyph());
excludeUtxoButton.setOnAction(event -> {
EventManager.get().post(new ExcludeUtxoEvent(walletTx, input));
});
label.setGraphic(excludeUtxoButton);
label.setContentDisplay(ContentDisplay.LEFT);
Tooltip tooltip = new Tooltip(); Tooltip tooltip = new Tooltip();
if(walletNode != null) { if(walletNode != null) {
@ -324,6 +338,13 @@ public class TransactionDiagram extends GridPane {
return spacer; return spacer;
} }
public static Glyph getExcludeGlyph() {
Glyph excludeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.TIMES_CIRCLE);
excludeGlyph.getStyleClass().add("exclude-utxo");
excludeGlyph.setFontSize(12);
return excludeGlyph;
}
public static Glyph getPaymentGlyph() { public static Glyph getPaymentGlyph() {
Glyph paymentGlyph = new Glyph("FontAwesome", FontAwesome.Glyph.SEND); Glyph paymentGlyph = new Glyph("FontAwesome", FontAwesome.Glyph.SEND);
paymentGlyph.getStyleClass().add("payment-icon"); paymentGlyph.getStyleClass().add("payment-icon");
@ -332,35 +353,35 @@ public class TransactionDiagram extends GridPane {
} }
public static Glyph getConsolidationGlyph() { public static Glyph getConsolidationGlyph() {
Glyph consolidationGlyph = new Glyph("Font Awesome 5 Free Solid", FontAwesome5.Glyph.REPLY_ALL); Glyph consolidationGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.REPLY_ALL);
consolidationGlyph.getStyleClass().add("consolidation-icon"); consolidationGlyph.getStyleClass().add("consolidation-icon");
consolidationGlyph.setFontSize(12); consolidationGlyph.setFontSize(12);
return consolidationGlyph; return consolidationGlyph;
} }
public static Glyph getChangeGlyph() { public static Glyph getChangeGlyph() {
Glyph changeGlyph = new Glyph("Font Awesome 5 Free Solid", FontAwesome5.Glyph.COINS); Glyph changeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.COINS);
changeGlyph.getStyleClass().add("change-icon"); changeGlyph.getStyleClass().add("change-icon");
changeGlyph.setFontSize(12); changeGlyph.setFontSize(12);
return changeGlyph; return changeGlyph;
} }
private Glyph getFeeGlyph() { private Glyph getFeeGlyph() {
Glyph feeGlyph = new Glyph("Font Awesome 5 Free Solid", FontAwesome5.Glyph.HAND_HOLDING); Glyph feeGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.HAND_HOLDING);
feeGlyph.getStyleClass().add("fee-icon"); feeGlyph.getStyleClass().add("fee-icon");
feeGlyph.setFontSize(12); feeGlyph.setFontSize(12);
return feeGlyph; return feeGlyph;
} }
private Glyph getWarningGlyph() { private Glyph getWarningGlyph() {
Glyph feeWarningGlyph = new Glyph("Font Awesome 5 Free Solid", FontAwesome5.Glyph.EXCLAMATION_CIRCLE); Glyph feeWarningGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_CIRCLE);
feeWarningGlyph.getStyleClass().add("fee-warning-icon"); feeWarningGlyph.getStyleClass().add("fee-warning-icon");
feeWarningGlyph.setFontSize(12); feeWarningGlyph.setFontSize(12);
return feeWarningGlyph; return feeWarningGlyph;
} }
private Glyph getLockGlyph() { private Glyph getLockGlyph() {
Glyph lockGlyph = new Glyph("Font Awesome 5 Free Solid", FontAwesome5.Glyph.LOCK); Glyph lockGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.LOCK);
lockGlyph.getStyleClass().add("lock-icon"); lockGlyph.getStyleClass().add("lock-icon");
lockGlyph.setFontSize(12); lockGlyph.setFontSize(12);
return lockGlyph; return lockGlyph;

View file

@ -0,0 +1,22 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
import com.sparrowwallet.drongo.wallet.WalletTransaction;
public class ExcludeUtxoEvent {
private final WalletTransaction walletTransaction;
private final BlockTransactionHashIndex utxo;
public ExcludeUtxoEvent(WalletTransaction walletTransaction, BlockTransactionHashIndex utxo) {
this.walletTransaction = walletTransaction;
this.utxo = utxo;
}
public WalletTransaction getWalletTransaction() {
return walletTransaction;
}
public BlockTransactionHashIndex getUtxo() {
return utxo;
}
}

View file

@ -40,6 +40,7 @@ public class FontAwesome5 extends GlyphFont {
SATELLITE_DISH('\uf7c0'), SATELLITE_DISH('\uf7c0'),
SD_CARD('\uf7c2'), SD_CARD('\uf7c2'),
SEARCH('\uf002'), SEARCH('\uf002'),
TIMES_CIRCLE('\uf057'),
TOOLS('\uf7d9'), TOOLS('\uf7d9'),
UNDO('\uf0e2'), UNDO('\uf0e2'),
WALLET('\uf555'); WALLET('\uf555');

View file

@ -93,6 +93,8 @@ public class SendController extends WalletFormController implements Initializabl
private final ObjectProperty<UtxoSelector> utxoSelectorProperty = new SimpleObjectProperty<>(null); private final ObjectProperty<UtxoSelector> utxoSelectorProperty = new SimpleObjectProperty<>(null);
private final ObjectProperty<UtxoFilter> utxoFilterProperty = new SimpleObjectProperty<>(null);
private final ObjectProperty<WalletTransaction> walletTransactionProperty = new SimpleObjectProperty<>(null); private final ObjectProperty<WalletTransaction> walletTransactionProperty = new SimpleObjectProperty<>(null);
private final BooleanProperty insufficientInputsProperty = new SimpleBooleanProperty(false); private final BooleanProperty insufficientInputsProperty = new SimpleBooleanProperty(false);
@ -256,16 +258,11 @@ public class SendController extends WalletFormController implements Initializabl
}); });
utxoSelectorProperty.addListener((observable, oldValue, utxoSelector) -> { utxoSelectorProperty.addListener((observable, oldValue, utxoSelector) -> {
if(utxoSelector instanceof PresetUtxoSelector) { updateMaxClearButtons(utxoSelector, utxoFilterProperty.get());
PresetUtxoSelector presetUtxoSelector = (PresetUtxoSelector)utxoSelector; });
int num = presetUtxoSelector.getPresetUtxos().size();
String selection = " (" + num + " UTXO" + (num > 1 ? "s" : "") + " selected)"; utxoFilterProperty.addListener((observable, oldValue, utxoFilter) -> {
maxButton.setText("Max" + selection); updateMaxClearButtons(utxoSelectorProperty.get(), utxoFilter);
clearButton.setText("Clear" + selection);
} else {
maxButton.setText("Max");
clearButton.setText("Clear");
}
}); });
walletTransactionProperty.addListener((observable, oldValue, walletTransaction) -> { walletTransactionProperty.addListener((observable, oldValue, walletTransaction) -> {
@ -328,7 +325,7 @@ public class SendController extends WalletFormController implements Initializabl
Integer currentBlockHeight = AppController.getCurrentBlockHeight(); Integer currentBlockHeight = AppController.getCurrentBlockHeight();
boolean groupByAddress = Config.get().isGroupByAddress(); boolean groupByAddress = Config.get().isGroupByAddress();
boolean includeMempoolChange = Config.get().isIncludeMempoolChange(); boolean includeMempoolChange = Config.get().isIncludeMempoolChange();
WalletTransaction walletTransaction = wallet.createWalletTransaction(getUtxoSelectors(), recipientAddress, recipientAmount, getFeeRate(), getMinimumFeeRate(), userFee, currentBlockHeight, sendAll, groupByAddress, includeMempoolChange); WalletTransaction walletTransaction = wallet.createWalletTransaction(getUtxoSelectors(), getUtxoFilters(), recipientAddress, recipientAmount, getFeeRate(), getMinimumFeeRate(), userFee, currentBlockHeight, sendAll, groupByAddress, includeMempoolChange);
walletTransactionProperty.setValue(walletTransaction); walletTransactionProperty.setValue(walletTransaction);
insufficientInputsProperty.set(false); insufficientInputsProperty.set(false);
@ -355,6 +352,15 @@ public class SendController extends WalletFormController implements Initializabl
return List.of(new BnBUtxoSelector(noInputsFee, costOfChange), new KnapsackUtxoSelector(noInputsFee)); return List.of(new BnBUtxoSelector(noInputsFee, costOfChange), new KnapsackUtxoSelector(noInputsFee));
} }
private List<UtxoFilter> getUtxoFilters() {
UtxoFilter utxoFilter = utxoFilterProperty.get();
if(utxoFilter != null) {
return List.of(utxoFilter);
}
return Collections.emptyList();
}
private boolean isValidRecipientAddress() { private boolean isValidRecipientAddress() {
try { try {
getRecipientAddress(); getRecipientAddress();
@ -471,10 +477,6 @@ public class SendController extends WalletFormController implements Initializabl
return targetBlocks.lookup(".thumb"); return targetBlocks.lookup(".thumb");
} }
public void setUtxoSelector(UtxoSelector utxoSelector) {
utxoSelectorProperty.set(utxoSelector);
}
public void setMaxInput(ActionEvent event) { public void setMaxInput(ActionEvent event) {
UtxoSelector utxoSelector = utxoSelectorProperty.get(); UtxoSelector utxoSelector = utxoSelectorProperty.get();
if(utxoSelector == null) { if(utxoSelector == null) {
@ -509,6 +511,25 @@ public class SendController extends WalletFormController implements Initializabl
return address.getScriptType().getDustThreshold(txOutput, getFeeRate()); return address.getScriptType().getDustThreshold(txOutput, getFeeRate());
} }
private void updateMaxClearButtons(UtxoSelector utxoSelector, UtxoFilter utxoFilter) {
if(utxoSelector instanceof PresetUtxoSelector) {
PresetUtxoSelector presetUtxoSelector = (PresetUtxoSelector)utxoSelector;
int num = presetUtxoSelector.getPresetUtxos().size();
String selection = " (" + num + " UTXO" + (num != 1 ? "s" : "") + " selected)";
maxButton.setText("Max" + selection);
clearButton.setText("Clear" + selection);
} else if(utxoFilter instanceof ExcludeUtxoFilter) {
ExcludeUtxoFilter excludeUtxoFilter = (ExcludeUtxoFilter)utxoFilter;
int num = excludeUtxoFilter.getExcludedUtxos().size();
String exclusion = " (" + num + " UTXO" + (num != 1 ? "s" : "") + " excluded)";
maxButton.setText("Max" + exclusion);
clearButton.setText("Clear" + exclusion);
} else {
maxButton.setText("Max");
clearButton.setText("Clear");
}
}
public void scanQrAddress(ActionEvent event) { public void scanQrAddress(ActionEvent event) {
QRScanDialog qrScanDialog = new QRScanDialog(); QRScanDialog qrScanDialog = new QRScanDialog();
Optional<QRScanDialog.Result> optionalResult = qrScanDialog.showAndWait(); Optional<QRScanDialog.Result> optionalResult = qrScanDialog.showAndWait();
@ -547,6 +568,7 @@ public class SendController extends WalletFormController implements Initializabl
userFeeSet.set(false); userFeeSet.set(false);
targetBlocks.setValue(4); targetBlocks.setValue(4);
utxoSelectorProperty.setValue(null); utxoSelectorProperty.setValue(null);
utxoFilterProperty.setValue(null);
walletTransactionProperty.setValue(null); walletTransactionProperty.setValue(null);
validationSupport.setErrorDecorationEnabled(false); validationSupport.setErrorDecorationEnabled(false);
@ -621,7 +643,8 @@ public class SendController extends WalletFormController implements Initializabl
public void spendUtxos(SpendUtxoEvent event) { public void spendUtxos(SpendUtxoEvent event) {
if(!event.getUtxoEntries().isEmpty() && event.getUtxoEntries().get(0).getWallet().equals(getWalletForm().getWallet())) { if(!event.getUtxoEntries().isEmpty() && event.getUtxoEntries().get(0).getWallet().equals(getWalletForm().getWallet())) {
List<BlockTransactionHashIndex> utxos = event.getUtxoEntries().stream().map(HashIndexEntry::getHashIndex).collect(Collectors.toList()); List<BlockTransactionHashIndex> utxos = event.getUtxoEntries().stream().map(HashIndexEntry::getHashIndex).collect(Collectors.toList());
setUtxoSelector(new PresetUtxoSelector(utxos)); utxoSelectorProperty.set(new PresetUtxoSelector(utxos));
utxoFilterProperty.set(null);
updateTransaction(true); updateTransaction(true);
} }
} }
@ -649,4 +672,37 @@ public class SendController extends WalletFormController implements Initializabl
setFiatAmount(event.getCurrencyRate(), getRecipientValueSats()); setFiatAmount(event.getCurrencyRate(), getRecipientValueSats());
setFiatFeeAmount(event.getCurrencyRate(), getFeeValueSats()); setFiatFeeAmount(event.getCurrencyRate(), getFeeValueSats());
} }
@Subscribe
public void excludeUtxo(ExcludeUtxoEvent event) {
if(event.getWalletTransaction() == walletTransactionProperty.get()) {
UtxoSelector utxoSelector = utxoSelectorProperty.get();
if(utxoSelector instanceof MaxUtxoSelector) {
Collection<BlockTransactionHashIndex> utxos = walletForm.getWallet().getWalletUtxos().keySet();
utxos.remove(event.getUtxo());
if(utxoFilterProperty.get() instanceof ExcludeUtxoFilter) {
ExcludeUtxoFilter existingUtxoFilter = (ExcludeUtxoFilter)utxoFilterProperty.get();
utxos.removeAll(existingUtxoFilter.getExcludedUtxos());
}
PresetUtxoSelector presetUtxoSelector = new PresetUtxoSelector(utxos);
utxoSelectorProperty.set(presetUtxoSelector);
updateTransaction(true);
} else if(utxoSelector instanceof PresetUtxoSelector) {
PresetUtxoSelector presetUtxoSelector = new PresetUtxoSelector(((PresetUtxoSelector)utxoSelector).getPresetUtxos());
presetUtxoSelector.getPresetUtxos().remove(event.getUtxo());
utxoSelectorProperty.set(presetUtxoSelector);
updateTransaction(true);
} else {
ExcludeUtxoFilter utxoFilter = new ExcludeUtxoFilter();
if(utxoFilterProperty.get() instanceof ExcludeUtxoFilter) {
ExcludeUtxoFilter existingUtxoFilter = (ExcludeUtxoFilter)utxoFilterProperty.get();
utxoFilter.getExcludedUtxos().addAll(existingUtxoFilter.getExcludedUtxos());
}
utxoFilter.getExcludedUtxos().add(event.getUtxo());
utxoFilterProperty.set(utxoFilter);
updateTransaction();
}
}
}
} }

View file

@ -66,3 +66,19 @@
-fx-stroke: #696c77; -fx-stroke: #696c77;
-fx-stroke-width: 1px; -fx-stroke-width: 1px;
} }
#transactionDiagram .utxo-label .button {
-fx-padding: 0;
-fx-pref-height: 18;
-fx-pref-width: 18;
-fx-border-width: 0;
-fx-background-color: -fx-background;
}
#transactionDiagram .utxo-label .button .label .text {
-fx-fill: -fx-background;
}
#transactionDiagram .utxo-label:hover .button .label .text {
-fx-fill: -fx-text-base-color;
}