WalletTemplate: introduce a simple wallet model and use the FX reactive bindings framework to clean up and fix some bugs. Empty wallet button now is disabled when the wallet is empty.

This commit is contained in:
Mike Hearn 2014-07-13 20:38:20 +02:00
parent 29a11e22b7
commit aa86642ffd
4 changed files with 87 additions and 37 deletions

View File

@ -1,9 +1,7 @@
package wallettemplate;
import com.google.bitcoin.core.AbstractWalletEventListener;
import com.google.bitcoin.core.Coin;
import com.google.bitcoin.core.DownloadListener;
import com.google.bitcoin.core.Wallet;
import javafx.animation.*;
import javafx.application.Platform;
import javafx.event.ActionEvent;
@ -14,11 +12,12 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
import wallettemplate.controls.ClickableBitcoinAddress;
import wallettemplate.utils.BitcoinUIModel;
import wallettemplate.utils.WTUtils;
import java.util.Date;
import static wallettemplate.Main.bitcoin;
import static wallettemplate.utils.GuiUtils.checkGuiThread;
/**
* Gets created auto-magically by FXMLLoader via reflection. The widget fields are set to the GUI controls they're named
@ -32,6 +31,8 @@ public class Controller {
public Button sendMoneyOutBtn;
public ClickableBitcoinAddress addressControl;
private BitcoinUIModel model;
// Called by FXMLLoader.
public void initialize() {
syncProgress.setProgress(-1);
@ -39,9 +40,11 @@ public class Controller {
}
public void onBitcoinSetup() {
bitcoin.wallet().addEventListener(new BalanceUpdater());
addressControl.setAddress(bitcoin.wallet().currentReceiveKey().toAddress(Main.params).toString());
refreshBalanceLabel();
model = new BitcoinUIModel(bitcoin.wallet());
addressControl.addressProperty().bind(model.addressProperty());
balance.textProperty().bind(WTUtils.bindToString(model.balanceProperty(), Coin::toPlainString));
// Don't let the user click send money when the wallet is empty.
sendMoneyOutBtn.disableProperty().bind(model.balanceProperty().isEqualTo(Coin.ZERO));
}
public void sendMoneyOut(ActionEvent event) {
@ -102,18 +105,4 @@ public class Controller {
public ProgressBarUpdater progressBarUpdater() {
return new ProgressBarUpdater();
}
public class BalanceUpdater extends AbstractWalletEventListener {
@Override
public void onWalletChanged(Wallet wallet) {
checkGuiThread();
refreshBalanceLabel();
// TODO: Refresh clickable address here.
}
}
public void refreshBalanceLabel() {
final Coin amount = bitcoin.wallet().getBalance(Wallet.BalanceType.ESTIMATED);
balance.setText(amount.toPlainString());
}
}

View File

@ -1,11 +1,13 @@
package wallettemplate.controls;
import com.google.bitcoin.core.Address;
import com.google.bitcoin.uri.BitcoinURI;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import javafx.beans.property.StringProperty;
import javafx.beans.binding.StringExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.ContextMenu;
@ -30,6 +32,8 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import static javafx.beans.binding.Bindings.convert;
// This control can be used with Scene Builder as long as we don't use any Java 8 features yet. Once Oracle release
// a new Scene Builder compiled against Java 8, we'll be able to use lambdas and so on here. Until that day comes,
// this file specifically must be recompiled against Java 7 for main.fxml to be editable visually.
@ -50,6 +54,9 @@ public class ClickableBitcoinAddress extends AnchorPane {
@FXML protected Label copyWidget;
@FXML protected Label qrCode;
protected SimpleObjectProperty<Address> address = new SimpleObjectProperty<>();
private final StringExpression addressStr;
public ClickableBitcoinAddress() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("bitcoin_address.fxml"));
@ -64,25 +71,28 @@ public class ClickableBitcoinAddress extends AnchorPane {
AwesomeDude.setIcon(qrCode, AwesomeIcon.QRCODE);
Tooltip.install(qrCode, new Tooltip("Show a barcode scannable with a mobile phone for this address"));
addressStr = convert(address);
addressLabel.textProperty().bind(addressStr);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String uri() {
return BitcoinURI.convertToBitcoinURI(getAddress(), null, Main.APP_NAME, null);
return BitcoinURI.convertToBitcoinURI(address.get(), null, Main.APP_NAME, null);
}
public String getAddress() {
return addressLabel.getText();
public Address getAddress() {
return address.get();
}
public void setAddress(String address) {
addressLabel.setText(address);
public void setAddress(Address address) {
this.address.set(address);
}
public StringProperty addressProperty() {
return addressLabel.textProperty();
public ObjectProperty<Address> addressProperty() {
return address;
}
@FXML
@ -90,8 +100,8 @@ public class ClickableBitcoinAddress extends AnchorPane {
// User clicked icon or menu item.
Clipboard clipboard = Clipboard.getSystemClipboard();
ClipboardContent content = new ClipboardContent();
content.putString(getAddress());
content.putHtml(String.format("<a href='%s'>%s</a>", uri(), getAddress()));
content.putString(addressStr.get());
content.putHtml(String.format("<a href='%s'>%s</a>", uri(), addressStr.get()));
clipboard.setContent(content);
}
@ -134,11 +144,6 @@ public class ClickableBitcoinAddress extends AnchorPane {
Pane pane = new Pane(view);
pane.setMaxSize(qrImage.getWidth(), qrImage.getHeight());
final Main.OverlayUI<ClickableBitcoinAddress> overlay = Main.instance.overlayUI(pane, this);
view.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
overlay.done();
}
});
view.setOnMouseClicked(event1 -> overlay.done());
}
}

View File

@ -0,0 +1,38 @@
package wallettemplate.utils;
import com.google.bitcoin.core.*;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
/**
* A class that exposes relevant bitcoin stuff as JavaFX bindable properties.
*/
public class BitcoinUIModel {
private SimpleObjectProperty<Address> address = new SimpleObjectProperty<>();
private SimpleObjectProperty<Coin> balance = new SimpleObjectProperty<>(Coin.ZERO);
public BitcoinUIModel(Wallet wallet) {
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public void onWalletChanged(Wallet wallet) {
super.onWalletChanged(wallet);
update(wallet);
}
}, Platform::runLater);
update(wallet);
}
private void update(Wallet wallet) {
balance.set(wallet.getBalance());
address.set(wallet.currentReceiveAddress());
}
public ReadOnlyObjectProperty<Address> addressProperty() {
return address;
}
public ReadOnlyObjectProperty<Coin> balanceProperty() {
return balance;
}
}

View File

@ -1,8 +1,12 @@
package wallettemplate.utils;
import javafx.beans.binding.StringBinding;
import javafx.beans.value.ObservableValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.Function;
/**
* Some generic utilities to make Java a bit less annoying.
*/
@ -67,4 +71,18 @@ public class WTUtils {
return true;
}
}
// Why isn't this a part of the JFX Bindings class?
public static <T> StringBinding bindToString(ObservableValue<T> value, Function<T, String> function) {
return new StringBinding() {
{
super.bind(value);
}
@Override
protected String computeValue() {
return function.apply(value.getValue());
}
};
}
}