package lighthouse; import com.subgraph.orchid.*; import com.vinumeris.updatefx.*; import de.jensd.fx.fontawesome.*; import javafx.animation.*; import javafx.application.*; import javafx.beans.*; import javafx.beans.property.*; import javafx.collections.*; import javafx.event.*; import javafx.fxml.*; import javafx.scene.control.*; import javafx.scene.layout.*; import kotlin.*; import kotlin.jvm.functions.*; import lighthouse.activities.*; import lighthouse.controls.*; import lighthouse.model.*; import lighthouse.nav.*; import lighthouse.protocol.*; import lighthouse.subwindows.*; import lighthouse.utils.*; import org.bitcoinj.core.*; import org.bitcoinj.params.*; import org.bitcoinj.utils.*; import org.fxmisc.easybind.*; import org.slf4j.*; import static javafx.beans.binding.Bindings.*; import static lighthouse.UpdateCheckStrings.*; import static lighthouse.utils.GuiUtils.*; import static lighthouse.utils.I18nUtil.*; /** * Gets created auto-magically by FXMLLoader via reflection. The widget fields are set to the GUI controls they're named * after. This class handles all the updates and event handling for the main UI. */ public class MainWindow { private static final Logger log = LoggerFactory.getLogger(MainWindow.class); @FXML HBox topBoxLeftArea; @FXML Label balance; @FXML Button emptyWalletBtn, setupWalletBtn, menuBtn; @FXML ClickableBitcoinAddress addressControl; @FXML HBox balanceArea; @FXML HBox topBox; @FXML VBox root; @FXML ScrollPane contentScrollPane; @FXML StackPane contentStack; @FXML Label networkIndicatorLabel; @FXML Button backButton; public static BitcoinUIModel bitcoinUIModel = new BitcoinUIModel(); private NotificationBarPane.Item syncItem; private ObservableMap<String, LighthouseBackend.ProjectStateInfo> projectStates; public static OverviewActivity overviewActivity; public static NavManager navManager; private static Updater updater; // Called by FXMLLoader. public void initialize() { Main.reached("MainWindow.initialize()"); AwesomeDude.setIcon(emptyWalletBtn, AwesomeIcon.SIGN_OUT, "12pt", ContentDisplay.LEFT); Tooltip.install(emptyWalletBtn, new Tooltip(tr("Send money out of the wallet"))); AwesomeDude.setIcon(setupWalletBtn, AwesomeIcon.LOCK, "12pt", ContentDisplay.LEFT); Tooltip.install(setupWalletBtn, new Tooltip(tr("Make paper backup and encrypt your wallet"))); AwesomeDude.setIcon(menuBtn, AwesomeIcon.BARS); LHUtils.stopwatch("Build overview activity", () -> overviewActivity = new OverviewActivity()); navManager = new NavManager(contentScrollPane, overviewActivity); // Slide back button in/out. AwesomeDude.setIcon(backButton, AwesomeIcon.ARROW_CIRCLE_LEFT, "30"); animatedBind(topBoxLeftArea, topBoxLeftArea.translateXProperty(), when(navManager.isOnInitialActivity()).then(-45).otherwise(0), Interpolator.EASE_OUT); } @FXML public void backButtonClicked(ActionEvent event) { navManager.back(); } private static boolean firstTime = true; public void onBitcoinSetup() { checkGuiThread(); bitcoinUIModel.setWallet(Main.wallet); if (Main.wallet.getExtensions().isEmpty()) { // TODO: i18n this after next release informationalAlert("Error loading wallet", "The Lighthouse specific wallet data failed to load properly. Your money is safe, but the " + "application may not recognise that you have pledged to projects. Please email contact@vinumeris.com " + "and request assistance. You should withdraw your funds using the 'empty wallet' button, which will " + "revoke any pledges you have made."); } addressControl.addressProperty().bind(bitcoinUIModel.addressProperty()); balance.textProperty().bind(EasyBind.map(bitcoinUIModel.balanceProperty(), coin -> MonetaryFormat.BTC.noCode().format(coin).toString())); configureEmptyWalletButton(); if (Main.params != MainNetParams.get()) { networkIndicatorLabel.setVisible(true); if (Main.params == TestNet3Params.get()) networkIndicatorLabel.setText("testnet"); else if (Main.params == RegTestParams.get()) networkIndicatorLabel.setText("regtest"); else networkIndicatorLabel.setText("?"); } configureOfflineNotification(); // Don't do startup processing if the UI is being hot reloaded ... if (firstTime) { firstTime = false; // NotificationBarPane is set up by this point, so we can do things that need to show notifications. setupBitcoinSyncNotification(); doOnlineUpdateCheck(); maybeShowReleaseNotes(); } } public void configureOfflineNotification() { synchronized (Main.bitcoin.getOffline()) { Main.bitcoin.getOffline().addListener(new InvalidationListener() { private NotificationBarPane.Item item; @Override public void invalidated(Observable ob) { Platform.runLater(() -> { if (Main.bitcoin.isOffline()) { item = Main.instance.notificationBar.displayNewItem( tr("You are offline. You will not be able to use the app until you go online and restart.")); emptyWalletBtn.disableProperty().unbind(); emptyWalletBtn.setDisable(true); } else { if (item != null) { item.cancel(); item = null; } emptyWalletBtn.setDisable(false); configureEmptyWalletButton(); } }); } }); } } public void configureEmptyWalletButton() { // Don't let the user click send money when the wallet is empty. emptyWalletBtn.disableProperty().bind(bitcoinUIModel.balanceProperty().isEqualTo(Coin.ZERO)); } private void maybeShowReleaseNotes() { // Show release notes when we've upgraded to a new version (hard coded), but only if this is the first run // after the upgrade. int lastRunVersion = Main.instance.prefs.getLastRunVersion(); if (lastRunVersion < Main.VERSION) { log.info("Was upgraded from v{} to v{}!", lastRunVersion, Main.VERSION); // // No release notes currently. // } Main.instance.prefs.setLastRunVersion(Main.VERSION); } private void setupBitcoinSyncNotification() { balance.setStyle("-fx-text-fill: grey"); TorClient torClient = Main.bitcoin.getPeers().getTorClient(); if (torClient != null) { SimpleDoubleProperty torProgress = new SimpleDoubleProperty(-1); String torMsg = tr("Initialising Tor"); syncItem = Main.instance.notificationBar.displayNewItem(torMsg, torProgress); torClient.addInitializationListener(new TorInitializationListener() { @Override public void initializationProgress(String message, int percent) { Platform.runLater(() -> { syncItem.label.set(torMsg + ": " + message); torProgress.set(percent / 100.0); }); } @Override public void initializationCompleted() { Platform.runLater(() -> { syncItem.cancel(); showBitcoinSyncMessage(); }); } }); } bitcoinUIModel.syncProgressProperty().addListener(x -> { double progress = bitcoinUIModel.syncProgressProperty().get(); if (progress >= 1.0) { if (syncItem != null) { // Hack around JFX progress animator leak bug. GuiUtils.runOnGuiThreadAfter(500, () -> { syncItem.cancel(); syncItem = null; balance.setStyle("-fx-text-fill: black"); }); } } else if (syncItem == null && progress > 0.0 && progress < 1.0) { showBitcoinSyncMessage(); } }); } @SuppressWarnings("ConstantConditions") private void doOnlineUpdateCheck() { if (Main.UPDATES_BASE_URL == null) { ((HBox)menuBtn.getParent()).getChildren().remove(menuBtn); return; } if (Main.bitcoin.isOffline()) return; OnlineUpdateChecks checker = new OnlineUpdateChecks(new Function2<UpdateState, OnlineUpdateChecks, Unit>() { // Only bother to show the user a notification if we're actually going to download an update. private NotificationBarPane.Item downloadingItem; @Override public Unit invoke(UpdateState state, OnlineUpdateChecks updater) { switch (state) { case DOWNLOADING: downloadingItem = Main.instance.notificationBar.displayNewItem(DOWNLOADING_SOFTWARE_UPDATE, updater.getProgress()); break; case AWAITING_APP_RESTART: Button btn = new Button(RESTART); btn.setOnAction(ev2 -> Main.restart()); NotificationBarPane.Item newItem = Main.instance.notificationBar.createItem(PLEASE_RESTART_NOW, btn); Main.instance.notificationBar.items.replaceAll(item -> item == downloadingItem ? newItem : item); break; case FAILED: downloadingItem.cancel(); break; } return null; } }); MainWindow.updater = checker.getUpdater(); // TODO: Refactor this } public void showBitcoinSyncMessage() { syncItem = Main.instance.notificationBar.displayNewItem(tr("Synchronising with the Bitcoin network"), bitcoinUIModel.syncProgressProperty()); } @FXML public void menuClicked(ActionEvent event) { // For now just skip straight to the only menu item: the update control panel. UpdateFXWindow.open(updater); } public void tellUserToSendSomeMoney() { GuiUtils.arrowBubbleToNode(balanceArea, tr("You don't have any bitcoins in this wallet")).thenRun(() -> { GuiUtils.arrowBubbleToNode(addressControl, tr("Send some money to this address first")); }); } @FXML public void emptyWallet(ActionEvent event) { EmptyWalletController.open(); } @FXML public void setupWalletClicked(ActionEvent event) { WalletSettingsController.open(null); } }