mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-31 15:22:16 +00:00
Wallet template: various updates.
Backport misc improvements from PayFile. Refactor the clickable address out into a custom widget. Use FontAwesome and the wrapper class for icons instead of a custom image. QRcode support.
This commit is contained in:
parent
6ec7880079
commit
387717c6c5
@ -306,7 +306,8 @@ public class BitcoinURI {
|
|||||||
* @param message A message
|
* @param message A message
|
||||||
* @return A String containing the Bitcoin URI
|
* @return A String containing the Bitcoin URI
|
||||||
*/
|
*/
|
||||||
public static String convertToBitcoinURI(String address, BigInteger amount, String label, String message) {
|
public static String convertToBitcoinURI(String address, @Nullable BigInteger amount, @Nullable String label,
|
||||||
|
@Nullable String message) {
|
||||||
checkNotNull(address);
|
checkNotNull(address);
|
||||||
if (amount != null && amount.compareTo(BigInteger.ZERO) < 0) {
|
if (amount != null && amount.compareTo(BigInteger.ZERO) < 0) {
|
||||||
throw new IllegalArgumentException("Amount must be positive");
|
throw new IllegalArgumentException("Amount must be positive");
|
||||||
|
@ -44,5 +44,15 @@
|
|||||||
<artifactId>aquafx</artifactId>
|
<artifactId>aquafx</artifactId>
|
||||||
<version>0.1</version>
|
<version>0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.jensd</groupId>
|
||||||
|
<artifactId>fontawesomefx</artifactId>
|
||||||
|
<version>8.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.glxn</groupId>
|
||||||
|
<artifactId>qrgen</artifactId>
|
||||||
|
<version>1.3</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
@ -1,27 +1,22 @@
|
|||||||
package wallettemplate;
|
package wallettemplate;
|
||||||
|
|
||||||
import com.google.bitcoin.core.*;
|
import com.google.bitcoin.core.AbstractWalletEventListener;
|
||||||
import com.google.bitcoin.uri.BitcoinURI;
|
import com.google.bitcoin.core.DownloadListener;
|
||||||
|
import com.google.bitcoin.core.Utils;
|
||||||
|
import com.google.bitcoin.core.Wallet;
|
||||||
import javafx.animation.*;
|
import javafx.animation.*;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.ContextMenu;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.control.ProgressBar;
|
||||||
import javafx.scene.input.Clipboard;
|
|
||||||
import javafx.scene.input.ClipboardContent;
|
|
||||||
import javafx.scene.input.MouseButton;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import wallettemplate.utils.GuiUtils;
|
import wallettemplate.controls.ClickableBitcoinAddress;
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
@ -36,66 +31,23 @@ public class Controller {
|
|||||||
public ProgressBar syncProgress;
|
public ProgressBar syncProgress;
|
||||||
public VBox syncBox;
|
public VBox syncBox;
|
||||||
public HBox controlsBox;
|
public HBox controlsBox;
|
||||||
public Label requestMoneyLink;
|
|
||||||
public Label balance;
|
public Label balance;
|
||||||
public ContextMenu addressMenu;
|
public ContextMenu addressMenu;
|
||||||
public HBox addressLabelBox;
|
|
||||||
public Button sendMoneyOutBtn;
|
public Button sendMoneyOutBtn;
|
||||||
public ImageView copyWidget;
|
public ClickableBitcoinAddress addressControl;
|
||||||
|
|
||||||
private Address primaryAddress;
|
|
||||||
|
|
||||||
// Called by FXMLLoader.
|
// Called by FXMLLoader.
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
syncProgress.setProgress(-1);
|
syncProgress.setProgress(-1);
|
||||||
addressLabelBox.setOpacity(0.0);
|
addressControl.setOpacity(0.0);
|
||||||
Tooltip tooltip = new Tooltip("Copy address to clipboard");
|
|
||||||
Tooltip.install(copyWidget, tooltip);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onBitcoinSetup() {
|
public void onBitcoinSetup() {
|
||||||
bitcoin.wallet().addEventListener(new BalanceUpdater());
|
bitcoin.wallet().addEventListener(new BalanceUpdater());
|
||||||
primaryAddress = bitcoin.wallet().getKeys().get(0).toAddress(Main.params);
|
addressControl.setAddress(bitcoin.wallet().getKeys().get(0).toAddress(Main.params).toString());
|
||||||
refreshBalanceLabel();
|
refreshBalanceLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestMoney(MouseEvent event) {
|
|
||||||
// User clicked on the address.
|
|
||||||
if (event.getButton() == MouseButton.SECONDARY || (event.getButton() == MouseButton.PRIMARY && event.isMetaDown())) {
|
|
||||||
addressMenu.show(requestMoneyLink, event.getScreenX(), event.getScreenY());
|
|
||||||
} else {
|
|
||||||
String uri = getURI();
|
|
||||||
System.out.println("Opening " + uri);
|
|
||||||
try {
|
|
||||||
Desktop.getDesktop().browse(URI.create(uri));
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Couldn't open wallet app.
|
|
||||||
GuiUtils.informationalAlert("Opening wallet app failed", "Perhaps you don't have one installed?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getURI() {
|
|
||||||
return BitcoinURI.convertToBitcoinURI(getAddress(), Utils.COIN, Main.APP_NAME, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getAddress() {
|
|
||||||
return primaryAddress.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void copyWidgetClicked(MouseEvent event) {
|
|
||||||
copyAddress(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void copyAddress(ActionEvent event) {
|
|
||||||
// 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>", getURI(), getAddress()));
|
|
||||||
clipboard.setContent(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendMoneyOut(ActionEvent event) {
|
public void sendMoneyOut(ActionEvent event) {
|
||||||
// Hide this UI and show the send money UI. This UI won't be clickable until the user dismisses send_money.
|
// Hide this UI and show the send money UI. This UI won't be clickable until the user dismisses send_money.
|
||||||
Main.instance.overlayUI("send_money.fxml");
|
Main.instance.overlayUI("send_money.fxml");
|
||||||
@ -122,8 +74,7 @@ public class Controller {
|
|||||||
// Buttons slide in and clickable address appears simultaneously.
|
// Buttons slide in and clickable address appears simultaneously.
|
||||||
TranslateTransition arrive = new TranslateTransition(Duration.millis(600), controlsBox);
|
TranslateTransition arrive = new TranslateTransition(Duration.millis(600), controlsBox);
|
||||||
arrive.setToY(0.0);
|
arrive.setToY(0.0);
|
||||||
requestMoneyLink.setText(primaryAddress.toString());
|
FadeTransition reveal = new FadeTransition(Duration.millis(500), addressControl);
|
||||||
FadeTransition reveal = new FadeTransition(Duration.millis(500), addressLabelBox);
|
|
||||||
reveal.setToValue(1.0);
|
reveal.setToValue(1.0);
|
||||||
ParallelTransition group = new ParallelTransition(arrive, reveal);
|
ParallelTransition group = new ParallelTransition(arrive, reveal);
|
||||||
// Slide out happens then slide in/fade happens.
|
// Slide out happens then slide in/fade happens.
|
||||||
|
@ -24,6 +24,8 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static wallettemplate.utils.GuiUtils.*;
|
||||||
|
|
||||||
public class Main extends Application {
|
public class Main extends Application {
|
||||||
public static String APP_NAME = "WalletTemplate";
|
public static String APP_NAME = "WalletTemplate";
|
||||||
|
|
||||||
@ -37,6 +39,7 @@ public class Main extends Application {
|
|||||||
@Override
|
@Override
|
||||||
public void start(Stage mainWindow) throws Exception {
|
public void start(Stage mainWindow) throws Exception {
|
||||||
instance = this;
|
instance = this;
|
||||||
|
// Show the crash dialog for any exceptions that we don't handle and that hit the main loop.
|
||||||
GuiUtils.handleCrashesOnThisThread();
|
GuiUtils.handleCrashesOnThisThread();
|
||||||
try {
|
try {
|
||||||
init(mainWindow);
|
init(mainWindow);
|
||||||
@ -57,7 +60,7 @@ public class Main extends Application {
|
|||||||
// Load the GUI. The Controller class will be automagically created and wired up.
|
// Load the GUI. The Controller class will be automagically created and wired up.
|
||||||
URL location = getClass().getResource("main.fxml");
|
URL location = getClass().getResource("main.fxml");
|
||||||
FXMLLoader loader = new FXMLLoader(location);
|
FXMLLoader loader = new FXMLLoader(location);
|
||||||
mainUI = (Pane) loader.load();
|
mainUI = loader.load();
|
||||||
Controller controller = loader.getController();
|
Controller controller = loader.getController();
|
||||||
// Configure the window with a StackPane so we can overlay things on top of the main UI.
|
// Configure the window with a StackPane so we can overlay things on top of the main UI.
|
||||||
uiStack = new StackPane(mainUI);
|
uiStack = new StackPane(mainUI);
|
||||||
@ -89,6 +92,7 @@ public class Main extends Application {
|
|||||||
// or progress widget to keep the user engaged whilst we initialise, but we don't.
|
// or progress widget to keep the user engaged whilst we initialise, but we don't.
|
||||||
bitcoin.setDownloadListener(controller.progressBarUpdater())
|
bitcoin.setDownloadListener(controller.progressBarUpdater())
|
||||||
.setBlockingStartup(false)
|
.setBlockingStartup(false)
|
||||||
|
.setUserAgent(APP_NAME, "1.0")
|
||||||
.startAndWait();
|
.startAndWait();
|
||||||
// Don't make the user wait for confirmations for now, as the intention is they're sending it their own money!
|
// Don't make the user wait for confirmations for now, as the intention is they're sending it their own money!
|
||||||
bitcoin.wallet().allowSpendingUnconfirmedTransactions();
|
bitcoin.wallet().allowSpendingUnconfirmedTransactions();
|
||||||
@ -97,40 +101,58 @@ public class Main extends Application {
|
|||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OverlayUI {
|
public class OverlayUI<T> {
|
||||||
Node ui;
|
public Node ui;
|
||||||
Object controller;
|
public T controller;
|
||||||
|
|
||||||
public OverlayUI(Node ui, Object controller) {
|
public OverlayUI(Node ui, T controller) {
|
||||||
this.ui = ui;
|
this.ui = ui;
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
blurOut(mainUI);
|
||||||
|
uiStack.getChildren().add(ui);
|
||||||
|
fadeIn(ui);
|
||||||
|
}
|
||||||
|
|
||||||
public void done() {
|
public void done() {
|
||||||
GuiUtils.fadeOutAndRemove(ui, uiStack);
|
checkGuiThread();
|
||||||
GuiUtils.blurIn(mainUI);
|
fadeOutAndRemove(ui, uiStack);
|
||||||
|
blurIn(mainUI);
|
||||||
this.ui = null;
|
this.ui = null;
|
||||||
this.controller = null;
|
this.controller = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loads the FXML file with the given name, blurs out the main UI and puts this one on top. */
|
public <T> OverlayUI<T> overlayUI(Node node, T controller) {
|
||||||
public OverlayUI overlayUI(String name) {
|
checkGuiThread();
|
||||||
try {
|
OverlayUI<T> pair = new OverlayUI<T>(node, controller);
|
||||||
// Load the UI from disk.
|
|
||||||
URL location = getClass().getResource(name);
|
|
||||||
FXMLLoader loader = new FXMLLoader(location);
|
|
||||||
Pane ui = (Pane) loader.load();
|
|
||||||
Object controller = loader.getController();
|
|
||||||
OverlayUI pair = new OverlayUI(ui, controller);
|
|
||||||
// Auto-magically set the overlayUi member, if it's there.
|
// Auto-magically set the overlayUi member, if it's there.
|
||||||
try {
|
try {
|
||||||
controller.getClass().getDeclaredField("overlayUi").set(controller, pair);
|
controller.getClass().getDeclaredField("overlayUi").set(controller, pair);
|
||||||
} catch (IllegalAccessException | NoSuchFieldException ignored) {
|
} catch (IllegalAccessException | NoSuchFieldException ignored) {
|
||||||
}
|
}
|
||||||
GuiUtils.blurOut(mainUI);
|
pair.show();
|
||||||
uiStack.getChildren().add(ui);
|
return pair;
|
||||||
GuiUtils.fadeIn(ui);
|
}
|
||||||
|
|
||||||
|
/** Loads the FXML file with the given name, blurs out the main UI and puts this one on top. */
|
||||||
|
public <T> OverlayUI<T> overlayUI(String name) {
|
||||||
|
try {
|
||||||
|
checkGuiThread();
|
||||||
|
// Load the UI from disk.
|
||||||
|
URL location = getClass().getResource(name);
|
||||||
|
FXMLLoader loader = new FXMLLoader(location);
|
||||||
|
Pane ui = loader.load();
|
||||||
|
T controller = loader.getController();
|
||||||
|
OverlayUI<T> pair = new OverlayUI<T>(ui, controller);
|
||||||
|
// Auto-magically set the overlayUi member, if it's there.
|
||||||
|
try {
|
||||||
|
controller.getClass().getDeclaredField("overlayUi").set(controller, pair);
|
||||||
|
} catch (IllegalAccessException | NoSuchFieldException ignored) {
|
||||||
|
}
|
||||||
|
pair.show();
|
||||||
return pair;
|
return pair;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e); // Can't happen.
|
throw new RuntimeException(e); // Can't happen.
|
||||||
|
@ -11,6 +11,7 @@ import javafx.event.ActionEvent;
|
|||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
|
import wallettemplate.controls.BitcoinAddressValidator;
|
||||||
import wallettemplate.utils.GuiUtils;
|
import wallettemplate.utils.GuiUtils;
|
||||||
|
|
||||||
public class SendMoneyController {
|
public class SendMoneyController {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package wallettemplate;
|
package wallettemplate.controls;
|
||||||
|
|
||||||
import com.google.bitcoin.core.Address;
|
import com.google.bitcoin.core.Address;
|
||||||
import com.google.bitcoin.core.AddressFormatException;
|
import com.google.bitcoin.core.AddressFormatException;
|
@ -0,0 +1,144 @@
|
|||||||
|
package wallettemplate.controls;
|
||||||
|
|
||||||
|
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.event.ActionEvent;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.control.ContextMenu;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.effect.DropShadow;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.input.Clipboard;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import net.glxn.qrgen.QRCode;
|
||||||
|
import net.glxn.qrgen.image.ImageType;
|
||||||
|
import wallettemplate.Main;
|
||||||
|
import wallettemplate.utils.GuiUtils;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// From the java directory:
|
||||||
|
//
|
||||||
|
// javac -cp $HOME/.m2/repository/net/glxn/qrgen/1.3/qrgen-1.3.jar:$HOME/.m2/repository/de/jensd/fontawesomefx/8.0.0/fontawesomefx-8.0.0.jar:../../../target/classes:../../../../core/target/bitcoinj-0.11-SNAPSHOT.jar -d ../../../target/classes/ -source 1.7 -target 1.7 wallettemplate/controls/ClickableBitcoinAddress.java
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom control that implements a clickable, copyable Bitcoin address. Clicking it opens a local wallet app. The
|
||||||
|
* address looks like a blue hyperlink. Next to it there are two icons, one that copies to the clipboard and another
|
||||||
|
* that shows a QRcode.
|
||||||
|
*/
|
||||||
|
public class ClickableBitcoinAddress extends AnchorPane {
|
||||||
|
@FXML protected Label addressLabel;
|
||||||
|
@FXML protected ContextMenu addressMenu;
|
||||||
|
@FXML protected Label copyWidget;
|
||||||
|
@FXML protected Label qrCode;
|
||||||
|
|
||||||
|
public ClickableBitcoinAddress() {
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("bitcoin_address.fxml"));
|
||||||
|
loader.setRoot(this);
|
||||||
|
loader.setController(this);
|
||||||
|
// The following line is supposed to help Scene Builder, although it doesn't seem to be needed for me.
|
||||||
|
loader.setClassLoader(getClass().getClassLoader());
|
||||||
|
loader.load();
|
||||||
|
|
||||||
|
AwesomeDude.setIcon(copyWidget, AwesomeIcon.COPY);
|
||||||
|
Tooltip.install(copyWidget, new Tooltip("Copy address to clipboard"));
|
||||||
|
|
||||||
|
AwesomeDude.setIcon(qrCode, AwesomeIcon.QRCODE);
|
||||||
|
Tooltip.install(qrCode, new Tooltip("Show a barcode scannable with a mobile phone for this address"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String uri() {
|
||||||
|
return BitcoinURI.convertToBitcoinURI(getAddress(), null, Main.APP_NAME, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return addressLabel.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(String address) {
|
||||||
|
addressLabel.setText(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringProperty addressProperty() {
|
||||||
|
return addressLabel.textProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
protected void copyAddress(ActionEvent event) {
|
||||||
|
// 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()));
|
||||||
|
clipboard.setContent(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
protected void requestMoney(MouseEvent event) {
|
||||||
|
if (event.getButton() == MouseButton.SECONDARY || (event.getButton() == MouseButton.PRIMARY && event.isMetaDown())) {
|
||||||
|
// User right clicked or the Mac equivalent. Show the context menu.
|
||||||
|
addressMenu.show(addressLabel, event.getScreenX(), event.getScreenY());
|
||||||
|
} else {
|
||||||
|
// User left clicked.
|
||||||
|
try {
|
||||||
|
Desktop.getDesktop().browse(URI.create(uri()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
GuiUtils.informationalAlert("Opening wallet app failed", "Perhaps you don't have one installed?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
protected void copyWidgetClicked(MouseEvent event) {
|
||||||
|
copyAddress(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
protected void showQRCode(MouseEvent event) {
|
||||||
|
// Serialize to PNG and back into an image. Pretty lame but it's the shortest code to write and I'm feeling
|
||||||
|
// lazy tonight.
|
||||||
|
final byte[] imageBytes = QRCode
|
||||||
|
.from(uri())
|
||||||
|
.withSize(320, 240)
|
||||||
|
.to(ImageType.PNG)
|
||||||
|
.stream()
|
||||||
|
.toByteArray();
|
||||||
|
Image qrImage = new Image(new ByteArrayInputStream(imageBytes));
|
||||||
|
ImageView view = new ImageView(qrImage);
|
||||||
|
view.setEffect(new DropShadow());
|
||||||
|
// Embed the image in a pane to ensure the drop-shadow interacts with the fade nicely, otherwise it looks weird.
|
||||||
|
// Then fix the width/height to stop it expanding to fill the parent, which would result in the image being
|
||||||
|
// non-centered on the screen. Finally fade/blur it in.
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,8 @@ import javafx.util.Duration;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
public class GuiUtils {
|
public class GuiUtils {
|
||||||
private static void runAlert(BiConsumer<Stage, AlertWindowController> setup) {
|
private static void runAlert(BiConsumer<Stage, AlertWindowController> setup) {
|
||||||
try {
|
try {
|
||||||
@ -24,8 +26,8 @@ public class GuiUtils {
|
|||||||
Stage dialogStage = new Stage();
|
Stage dialogStage = new Stage();
|
||||||
dialogStage.initModality(Modality.WINDOW_MODAL);
|
dialogStage.initModality(Modality.WINDOW_MODAL);
|
||||||
FXMLLoader loader = new FXMLLoader(GuiUtils.class.getResource("alert.fxml"));
|
FXMLLoader loader = new FXMLLoader(GuiUtils.class.getResource("alert.fxml"));
|
||||||
Pane pane = (Pane) loader.load();
|
Pane pane = loader.load();
|
||||||
AlertWindowController controller = (AlertWindowController) loader.getController();
|
AlertWindowController controller = loader.getController();
|
||||||
setup.accept(dialogStage, controller);
|
setup.accept(dialogStage, controller);
|
||||||
dialogStage.setScene(new Scene(pane));
|
dialogStage.setScene(new Scene(pane));
|
||||||
dialogStage.showAndWait();
|
dialogStage.showAndWait();
|
||||||
@ -52,8 +54,9 @@ public class GuiUtils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void informationalAlert(String message, String details) {
|
public static void informationalAlert(String message, String details, Object... args) {
|
||||||
Runnable r = () -> runAlert((stage, controller) -> controller.informational(stage, message, details));
|
String formattedDetails = String.format(details, args);
|
||||||
|
Runnable r = () -> runAlert((stage, controller) -> controller.informational(stage, message, formattedDetails));
|
||||||
if (Platform.isFxApplicationThread())
|
if (Platform.isFxApplicationThread())
|
||||||
r.run();
|
r.run();
|
||||||
else
|
else
|
||||||
@ -102,4 +105,8 @@ public class GuiUtils {
|
|||||||
timeline.setOnFinished(actionEvent -> node.setEffect(null));
|
timeline.setOnFinished(actionEvent -> node.setEffect(null));
|
||||||
timeline.play();
|
timeline.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void checkGuiThread() {
|
||||||
|
checkState(Platform.isFxApplicationThread());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package wallettemplate.utils;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple wrapper around {@link javafx.application.Platform#runLater(Runnable)} which will do nothing if the previous
|
||||||
|
* invocation of runLater didn't execute on the JavaFX UI thread yet. In this way you can avoid flooding
|
||||||
|
* the event loop if you have a background thread that for whatever reason wants to update the UI very
|
||||||
|
* frequently. Without this class you could end up bloating up memory usage and causing the UI to stutter
|
||||||
|
* if the UI thread couldn't keep up with your background worker.
|
||||||
|
*/
|
||||||
|
public class ThrottledRunLater implements Runnable {
|
||||||
|
private final Runnable runnable;
|
||||||
|
private final AtomicBoolean pending = new AtomicBoolean();
|
||||||
|
|
||||||
|
/** Created this way, the no-args runLater will execute this classes run method. */
|
||||||
|
public ThrottledRunLater() {
|
||||||
|
this.runnable = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Created this way, the no-args runLater will execute the given runnable. */
|
||||||
|
public ThrottledRunLater(Runnable runnable) {
|
||||||
|
this.runnable = runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runLater(Runnable runnable) {
|
||||||
|
if (!pending.getAndSet(true)) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
pending.set(false);
|
||||||
|
runnable.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void runLater() {
|
||||||
|
runLater(runnable != null ? runnable : this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Before Width: | Height: | Size: 1010 B |
@ -1,5 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?scenebuilder-classpath-element ../../../../target/classes?>
|
||||||
|
<?scenebuilder-classpath-element ../../../../../core/target/bitcoinj-0.11-SNAPSHOT.jar?>
|
||||||
<?import java.lang.*?>
|
<?import java.lang.*?>
|
||||||
<?import java.util.*?>
|
<?import java.util.*?>
|
||||||
<?import javafx.geometry.*?>
|
<?import javafx.geometry.*?>
|
||||||
@ -10,6 +12,8 @@
|
|||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import javafx.scene.paint.*?>
|
<?import javafx.scene.paint.*?>
|
||||||
<?import javafx.scene.text.*?>
|
<?import javafx.scene.text.*?>
|
||||||
|
<?import wallettemplate.controls.*?>
|
||||||
|
<?import wallettemplate.controls.ClickableBitcoinAddress ?>
|
||||||
|
|
||||||
<AnchorPane id="AnchorPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="200.0" minWidth="300.0" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="wallettemplate.Controller">
|
<AnchorPane id="AnchorPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="200.0" minWidth="300.0" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="wallettemplate.Controller">
|
||||||
<children>
|
<children>
|
||||||
@ -59,31 +63,7 @@
|
|||||||
<Image url="@bitcoin_logo_plain.png" />
|
<Image url="@bitcoin_logo_plain.png" />
|
||||||
</image>
|
</image>
|
||||||
</ImageView>
|
</ImageView>
|
||||||
<HBox fx:id="addressLabelBox" alignment="CENTER_LEFT" layoutY="45.0" prefHeight="21.0" prefWidth="391.0" spacing="10.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="195.0">
|
<ClickableBitcoinAddress fx:id="addressControl" layoutY="45.0" prefHeight="21.0" prefWidth="391.0" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="195.0" />
|
||||||
<children>
|
|
||||||
<Label fx:id="requestMoneyLink" onMouseClicked="#requestMoney" style="-fx-cursor: hand" text="<address goes here>" textFill="BLUE" underline="true">
|
|
||||||
<contextMenu>
|
|
||||||
<ContextMenu fx:id="addressMenu">
|
|
||||||
<items>
|
|
||||||
<MenuItem mnemonicParsing="false" onAction="#copyAddress" text="Copy to clipboard">
|
|
||||||
<accelerator>
|
|
||||||
<KeyCodeCombination alt="UP" code="C" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
|
|
||||||
</accelerator>
|
|
||||||
</MenuItem>
|
|
||||||
</items>
|
|
||||||
</ContextMenu>
|
|
||||||
</contextMenu>
|
|
||||||
</Label>
|
|
||||||
<ImageView fx:id="copyWidget" fitHeight="16.0" fitWidth="16.0" focusTraversable="true" onMouseClicked="#copyWidgetClicked" pickOnBounds="true" preserveRatio="true" smooth="true">
|
|
||||||
<image>
|
|
||||||
<Image url="@copy-icon.png" />
|
|
||||||
</image>
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets />
|
|
||||||
</HBox.margin>
|
|
||||||
</ImageView>
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
<StackPane id="connectionsListView" layoutX="14.0" layoutY="81.0" prefHeight="249.0" prefWidth="572.0">
|
<StackPane id="connectionsListView" layoutX="14.0" layoutY="81.0" prefHeight="249.0" prefWidth="572.0">
|
||||||
<children>
|
<children>
|
||||||
<Label text="Your content goes here" />
|
<Label text="Your content goes here" />
|
||||||
|
Loading…
Reference in New Issue
Block a user