mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-03-10 09:20:04 +01:00
walletfx: move OverlayUI from Main to MainController
Move OverlayUI and other related functionality from Main (Application) class to MainController. Motivation: 1. This simplifies the Main class 2. The code more logically belongs in the controller 3. The code being in the controller increases reusability 4. Is a first step towards additional refactoring made possible because MainController can subclass an abstract class and Main can’t because it must subclass Application
This commit is contained in:
parent
1e7fc7aad5
commit
17aeea2d75
7 changed files with 144 additions and 125 deletions
|
@ -30,21 +30,17 @@ import org.bitcoinj.wallet.DeterministicSeed;
|
|||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import wallettemplate.controls.NotificationBarPane;
|
||||
import org.bitcoinj.walletfx.utils.GuiUtils;
|
||||
import org.bitcoinj.walletfx.utils.TextFieldValidator;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import static org.bitcoinj.walletfx.utils.GuiUtils.*;
|
||||
import static org.bitcoinj.walletfx.utils.GuiUtils.informationalAlert;
|
||||
|
||||
public class Main extends Application {
|
||||
public static NetworkParameters params = TestNet3Params.get();
|
||||
|
@ -56,11 +52,7 @@ public class Main extends Application {
|
|||
public static WalletAppKit bitcoin;
|
||||
public static Main instance;
|
||||
|
||||
private StackPane uiStack;
|
||||
private Pane mainUI;
|
||||
public MainController controller;
|
||||
public NotificationBarPane notificationBar;
|
||||
public Stage mainWindow;
|
||||
|
||||
@Override
|
||||
public void start(Stage mainWindow) throws Exception {
|
||||
|
@ -73,7 +65,6 @@ public class Main extends Application {
|
|||
}
|
||||
|
||||
private void realStart(Stage mainWindow) throws IOException {
|
||||
this.mainWindow = mainWindow;
|
||||
instance = this;
|
||||
// Show the crash dialog for any exceptions that we don't handle and that hit the main loop.
|
||||
GuiUtils.handleCrashesOnThisThread();
|
||||
|
@ -87,19 +78,10 @@ public class Main extends Application {
|
|||
// Load the GUI. The MainController class will be automagically created and wired up.
|
||||
URL location = getClass().getResource("main.fxml");
|
||||
FXMLLoader loader = new FXMLLoader(location);
|
||||
mainUI = loader.load();
|
||||
Pane mainUI = loader.load();
|
||||
controller = loader.getController();
|
||||
// Configure the window with a StackPane so we can overlay things on top of the main UI, and a
|
||||
// NotificationBarPane so we can slide messages and progress bars in from the bottom. Note that
|
||||
// ordering of the construction and connection matters here, otherwise we get (harmless) CSS error
|
||||
// spew to the logs.
|
||||
notificationBar = new NotificationBarPane(mainUI);
|
||||
mainWindow.setTitle(APP_NAME);
|
||||
uiStack = new StackPane();
|
||||
Scene scene = new Scene(uiStack);
|
||||
TextFieldValidator.configureScene(scene); // Add CSS that we need.
|
||||
scene.getStylesheets().add(getClass().getResource("wallet.css").toString());
|
||||
uiStack.getChildren().add(notificationBar);
|
||||
|
||||
Scene scene = controller.controllerStart(mainUI, "wallet.css");
|
||||
mainWindow.setScene(scene);
|
||||
|
||||
// Make log output concise.
|
||||
|
@ -154,94 +136,6 @@ public class Main extends Application {
|
|||
bitcoin.restoreWalletFromSeed(seed);
|
||||
}
|
||||
|
||||
private Node stopClickPane = new Pane();
|
||||
|
||||
public class OverlayUI<T> {
|
||||
public Node ui;
|
||||
public T controller;
|
||||
|
||||
public OverlayUI(Node ui, T controller) {
|
||||
this.ui = ui;
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
checkGuiThread();
|
||||
if (currentOverlay == null) {
|
||||
uiStack.getChildren().add(stopClickPane);
|
||||
uiStack.getChildren().add(ui);
|
||||
blurOut(mainUI);
|
||||
//darken(mainUI);
|
||||
fadeIn(ui);
|
||||
zoomIn(ui);
|
||||
} else {
|
||||
// Do a quick transition between the current overlay and the next.
|
||||
// Bug here: we don't pay attention to changes in outsideClickDismisses.
|
||||
explodeOut(currentOverlay.ui);
|
||||
fadeOutAndRemove(uiStack, currentOverlay.ui);
|
||||
uiStack.getChildren().add(ui);
|
||||
ui.setOpacity(0.0);
|
||||
fadeIn(ui, 100);
|
||||
zoomIn(ui, 100);
|
||||
}
|
||||
currentOverlay = this;
|
||||
}
|
||||
|
||||
public void outsideClickDismisses() {
|
||||
stopClickPane.setOnMouseClicked((ev) -> done());
|
||||
}
|
||||
|
||||
public void done() {
|
||||
checkGuiThread();
|
||||
if (ui == null) return; // In the middle of being dismissed and got an extra click.
|
||||
explodeOut(ui);
|
||||
fadeOutAndRemove(uiStack, ui, stopClickPane);
|
||||
blurIn(mainUI);
|
||||
//undark(mainUI);
|
||||
this.ui = null;
|
||||
this.controller = null;
|
||||
currentOverlay = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private OverlayUI currentOverlay;
|
||||
|
||||
public <T> OverlayUI<T> overlayUI(Node node, T controller) {
|
||||
checkGuiThread();
|
||||
OverlayUI<T> pair = new OverlayUI<>(node, controller);
|
||||
// Auto-magically set the overlayUI member, if it's there.
|
||||
try {
|
||||
controller.getClass().getField("overlayUI").set(controller, pair);
|
||||
} catch (IllegalAccessException | NoSuchFieldException ignored) {
|
||||
}
|
||||
pair.show();
|
||||
return pair;
|
||||
}
|
||||
|
||||
/** 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 = GuiUtils.getResource(name);
|
||||
FXMLLoader loader = new FXMLLoader(location);
|
||||
Pane ui = loader.load();
|
||||
T controller = loader.getController();
|
||||
OverlayUI<T> pair = new OverlayUI<>(ui, controller);
|
||||
// Auto-magically set the overlayUI member, if it's there.
|
||||
try {
|
||||
if (controller != null)
|
||||
controller.getClass().getField("overlayUI").set(controller, pair);
|
||||
} catch (IllegalAccessException | NoSuchFieldException ignored) {
|
||||
ignored.printStackTrace();
|
||||
}
|
||||
pair.show();
|
||||
return pair;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // Can't happen.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
|
|
|
@ -19,6 +19,12 @@ package wallettemplate;
|
|||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.input.KeyCombination;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.bitcoinj.core.listeners.DownloadProgressTracker;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.utils.MonetaryFormat;
|
||||
|
@ -30,6 +36,8 @@ import javafx.scene.control.Button;
|
|||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.util.Duration;
|
||||
import org.bitcoinj.walletfx.utils.GuiUtils;
|
||||
import org.bitcoinj.walletfx.utils.TextFieldValidator;
|
||||
import wallettemplate.controls.ClickableBitcoinAddress;
|
||||
import wallettemplate.controls.NotificationBarPane;
|
||||
import org.bitcoinj.walletfx.utils.BitcoinUIModel;
|
||||
|
@ -37,6 +45,11 @@ import org.bitcoinj.walletfx.utils.GuiUtils;
|
|||
import org.bitcoinj.walletfx.utils.easing.EasingMode;
|
||||
import org.bitcoinj.walletfx.utils.easing.ElasticInterpolator;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import static org.bitcoinj.walletfx.utils.GuiUtils.*;
|
||||
import static wallettemplate.Main.bitcoin;
|
||||
|
||||
/**
|
||||
|
@ -44,20 +57,44 @@ import static wallettemplate.Main.bitcoin;
|
|||
* after. This class handles all the updates and event handling for the main UI.
|
||||
*/
|
||||
public class MainController {
|
||||
public static MainController instance;
|
||||
public HBox controlsBox;
|
||||
public Label balance;
|
||||
public Button sendMoneyOutBtn;
|
||||
public ClickableBitcoinAddress addressControl;
|
||||
|
||||
private BitcoinUIModel model = new BitcoinUIModel();
|
||||
private final BitcoinUIModel model = new BitcoinUIModel();
|
||||
private NotificationBarPane.Item syncItem;
|
||||
private static final MonetaryFormat MONETARY_FORMAT = MonetaryFormat.BTC.noCode();
|
||||
|
||||
private Pane mainUI;
|
||||
private StackPane uiStack;
|
||||
private NotificationBarPane notificationBar;
|
||||
private final Node stopClickPane = new Pane();
|
||||
|
||||
// Called by FXMLLoader.
|
||||
public void initialize() {
|
||||
instance = this;
|
||||
addressControl.setOpacity(0.0);
|
||||
}
|
||||
|
||||
Scene controllerStart(Pane mainUI, String cssResourceName) {
|
||||
this.mainUI = mainUI;
|
||||
// Configure the window with a StackPane so we can overlay things on top of the main UI, and a
|
||||
// NotificationBarPane so we can slide messages and progress bars in from the bottom. Note that
|
||||
// ordering of the construction and connection matters here, otherwise we get (harmless) CSS error
|
||||
// spew to the logs.
|
||||
notificationBar = new NotificationBarPane(mainUI);
|
||||
uiStack = new StackPane();
|
||||
Scene scene = new Scene(uiStack);
|
||||
TextFieldValidator.configureScene(scene);
|
||||
// Add CSS that we need. cssResourceName will be loaded from the same package as this class.
|
||||
scene.getStylesheets().add(getClass().getResource(cssResourceName).toString());
|
||||
uiStack.getChildren().add(notificationBar);
|
||||
scene.getAccelerators().put(KeyCombination.valueOf("Shortcut+F"), () -> bitcoin.peerGroup().getDownloadPeer().close());
|
||||
return scene;
|
||||
}
|
||||
|
||||
public void onBitcoinSetup() {
|
||||
model.setWallet(bitcoin.wallet());
|
||||
addressControl.addressProperty().bind(model.addressProperty());
|
||||
|
@ -88,16 +125,16 @@ public class MainController {
|
|||
}
|
||||
|
||||
private void showBitcoinSyncMessage() {
|
||||
syncItem = Main.instance.notificationBar.pushItem("Synchronising with the Bitcoin network", model.syncProgressProperty());
|
||||
syncItem = notificationBar.pushItem("Synchronising with the Bitcoin network", model.syncProgressProperty());
|
||||
}
|
||||
|
||||
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.
|
||||
Main.instance.overlayUI("send_money.fxml");
|
||||
overlayUI("send_money.fxml");
|
||||
}
|
||||
|
||||
public void settingsClicked(ActionEvent event) {
|
||||
Main.OverlayUI<WalletSettingsController> screen = Main.instance.overlayUI("wallet_settings.fxml");
|
||||
OverlayUI<WalletSettingsController> screen = overlayUI("wallet_settings.fxml");
|
||||
screen.controller.initialize(null);
|
||||
}
|
||||
|
||||
|
@ -132,4 +169,91 @@ public class MainController {
|
|||
public DownloadProgressTracker progressBarUpdater() {
|
||||
return model.getDownloadProgressTracker();
|
||||
}
|
||||
|
||||
public class OverlayUI<T> {
|
||||
public Node ui;
|
||||
public T controller;
|
||||
|
||||
public OverlayUI(Node ui, T controller) {
|
||||
this.ui = ui;
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
checkGuiThread();
|
||||
if (currentOverlay == null) {
|
||||
uiStack.getChildren().add(stopClickPane);
|
||||
uiStack.getChildren().add(ui);
|
||||
blurOut(mainUI);
|
||||
//darken(mainUI);
|
||||
fadeIn(ui);
|
||||
zoomIn(ui);
|
||||
} else {
|
||||
// Do a quick transition between the current overlay and the next.
|
||||
// Bug here: we don't pay attention to changes in outsideClickDismisses.
|
||||
explodeOut(currentOverlay.ui);
|
||||
fadeOutAndRemove(uiStack, currentOverlay.ui);
|
||||
uiStack.getChildren().add(ui);
|
||||
ui.setOpacity(0.0);
|
||||
fadeIn(ui, 100);
|
||||
zoomIn(ui, 100);
|
||||
}
|
||||
currentOverlay = this;
|
||||
}
|
||||
|
||||
public void outsideClickDismisses() {
|
||||
stopClickPane.setOnMouseClicked((ev) -> done());
|
||||
}
|
||||
|
||||
public void done() {
|
||||
checkGuiThread();
|
||||
if (ui == null) return; // In the middle of being dismissed and got an extra click.
|
||||
explodeOut(ui);
|
||||
fadeOutAndRemove(uiStack, ui, stopClickPane);
|
||||
blurIn(mainUI);
|
||||
//undark(mainUI);
|
||||
this.ui = null;
|
||||
this.controller = null;
|
||||
currentOverlay = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private OverlayUI currentOverlay;
|
||||
|
||||
public <T> OverlayUI<T> overlayUI(Node node, T controller) {
|
||||
checkGuiThread();
|
||||
OverlayUI<T> pair = new OverlayUI<>(node, controller);
|
||||
// Auto-magically set the overlayUI member, if it's there.
|
||||
try {
|
||||
controller.getClass().getField("overlayUI").set(controller, pair);
|
||||
} catch (IllegalAccessException | NoSuchFieldException ignored) {
|
||||
}
|
||||
pair.show();
|
||||
return pair;
|
||||
}
|
||||
|
||||
/** 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 = GuiUtils.getResource(name);
|
||||
FXMLLoader loader = new FXMLLoader(location);
|
||||
Pane ui = loader.load();
|
||||
T controller = loader.getController();
|
||||
OverlayUI<T> pair = new OverlayUI<>(ui, controller);
|
||||
// Auto-magically set the overlayUI member, if it's there.
|
||||
try {
|
||||
if (controller != null)
|
||||
controller.getClass().getField("overlayUI").set(controller, pair);
|
||||
} catch (IllegalAccessException | NoSuchFieldException ignored) {
|
||||
ignored.printStackTrace();
|
||||
}
|
||||
pair.show();
|
||||
return pair;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // Can't happen.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ public class SendMoneyController {
|
|||
public TextField amountEdit;
|
||||
public Label btcLabel;
|
||||
|
||||
public Main.OverlayUI overlayUI;
|
||||
public MainController.OverlayUI overlayUI;
|
||||
|
||||
private Wallet.SendResult sendResult;
|
||||
private KeyParameter aesKey;
|
||||
|
@ -114,14 +114,14 @@ public class SendMoneyController {
|
|||
}
|
||||
|
||||
private void askForPasswordAndRetry() {
|
||||
Main.OverlayUI<WalletPasswordController> pwd = Main.instance.overlayUI("wallet_password.fxml");
|
||||
MainController.OverlayUI<WalletPasswordController> pwd = MainController.instance.overlayUI("wallet_password.fxml");
|
||||
final String addressStr = address.getText();
|
||||
final String amountStr = amountEdit.getText();
|
||||
pwd.controller.aesKeyProperty().addListener((observable, old, cur) -> {
|
||||
// We only get here if the user found the right password. If they don't or they cancel, we end up back on
|
||||
// the main UI screen. By now the send money screen is history so we must recreate it.
|
||||
checkGuiThread();
|
||||
Main.OverlayUI<SendMoneyController> screen = Main.instance.overlayUI("send_money.fxml");
|
||||
MainController.OverlayUI<SendMoneyController> screen = MainController.instance.overlayUI("send_money.fxml");
|
||||
screen.controller.aesKey = cur;
|
||||
screen.controller.address.setText(addressStr);
|
||||
screen.controller.amountEdit.setText(amountStr);
|
||||
|
|
|
@ -54,7 +54,7 @@ public class WalletPasswordController {
|
|||
@FXML GridPane widgetGrid;
|
||||
@FXML Label explanationLabel;
|
||||
|
||||
public Main.OverlayUI overlayUI;
|
||||
public MainController.OverlayUI overlayUI;
|
||||
|
||||
private SimpleObjectProperty<KeyParameter> aesKey = new SimpleObjectProperty<>();
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ public class WalletSetPasswordController {
|
|||
public Button closeButton;
|
||||
public Label explanationLabel;
|
||||
|
||||
public Main.OverlayUI overlayUI;
|
||||
public MainController.OverlayUI overlayUI;
|
||||
// These params were determined empirically on a top-range (as of 2014) MacBook Pro with native scrypt support,
|
||||
// using the scryptenc command line tool from the original scrypt distribution, given a memory limit of 40mb.
|
||||
public static final Protos.ScryptParameters SCRYPT_PARAMETERS = Protos.ScryptParameters.newBuilder()
|
||||
|
|
|
@ -55,7 +55,7 @@ public class WalletSettingsController {
|
|||
@FXML TextArea wordsArea;
|
||||
@FXML Button restoreButton;
|
||||
|
||||
public Main.OverlayUI overlayUI;
|
||||
public MainController.OverlayUI overlayUI;
|
||||
|
||||
private KeyParameter aesKey;
|
||||
|
||||
|
@ -132,12 +132,12 @@ public class WalletSettingsController {
|
|||
}
|
||||
|
||||
private void askForPasswordAndRetry() {
|
||||
Main.OverlayUI<WalletPasswordController> pwd = Main.instance.overlayUI("wallet_password.fxml");
|
||||
MainController.OverlayUI<WalletPasswordController> pwd = MainController.instance.overlayUI("wallet_password.fxml");
|
||||
pwd.controller.aesKeyProperty().addListener((observable, old, cur) -> {
|
||||
// We only get here if the user found the right password. If they don't or they cancel, we end up back on
|
||||
// the main UI screen.
|
||||
checkGuiThread();
|
||||
Main.OverlayUI<WalletSettingsController> screen = Main.instance.overlayUI("wallet_settings.fxml");
|
||||
MainController.OverlayUI<WalletSettingsController> screen = MainController.instance.overlayUI("wallet_settings.fxml");
|
||||
screen.controller.initialize(cur);
|
||||
});
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ public class WalletSettingsController {
|
|||
informationalAlert("Wallet restore in progress",
|
||||
"Your wallet will now be resynced from the Bitcoin network. This can take a long time for old wallets.");
|
||||
overlayUI.done();
|
||||
Main.instance.controller.restoreFromSeedAnimation();
|
||||
MainController.instance.restoreFromSeedAnimation();
|
||||
|
||||
long birthday = datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC);
|
||||
DeterministicSeed seed = new DeterministicSeed(Splitter.on(' ').splitToList(wordsArea.getText()), null, "", birthday);
|
||||
|
@ -184,7 +184,7 @@ public class WalletSettingsController {
|
|||
|
||||
public void passwordButtonClicked(ActionEvent event) {
|
||||
if (aesKey == null) {
|
||||
Main.instance.overlayUI("wallet_set_password.fxml");
|
||||
MainController.instance.overlayUI("wallet_set_password.fxml");
|
||||
} else {
|
||||
Main.bitcoin.wallet().decrypt(aesKey);
|
||||
informationalAlert("Wallet decrypted", "A password will no longer be required to send money or edit settings.");
|
||||
|
|
|
@ -48,6 +48,7 @@ import org.bitcoinj.walletfx.utils.QRCodeImages;
|
|||
|
||||
import de.jensd.fx.fontawesome.AwesomeDude;
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
import wallettemplate.MainController;
|
||||
|
||||
import static javafx.beans.binding.Bindings.convert;
|
||||
|
||||
|
@ -143,7 +144,7 @@ public class ClickableBitcoinAddress extends AnchorPane {
|
|||
// 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);
|
||||
final MainController.OverlayUI<ClickableBitcoinAddress> overlay = MainController.instance.overlayUI(pane, this);
|
||||
view.setOnMouseClicked(event1 -> overlay.done());
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue