diff --git a/src/main/java/io/bitsquare/btc/AddressBasedCoinSelector.java b/src/main/java/io/bitsquare/btc/AddressBasedCoinSelector.java index b1260cd2fc..16313344fb 100644 --- a/src/main/java/io/bitsquare/btc/AddressBasedCoinSelector.java +++ b/src/main/java/io/bitsquare/btc/AddressBasedCoinSelector.java @@ -95,14 +95,20 @@ class AddressBasedCoinSelector extends DefaultCoinSelector { // TODO It might be risky to accept 0 confirmation tx from the network with only > 1 numBroadcastPeers // Need to be tested in testnet and mainnet // We need to handle cases when malleability happens or tx get lost and have not been successful propagated - return type.equals(TransactionConfidence.ConfidenceType.BUILDING) || + /* return type.equals(TransactionConfidence.ConfidenceType.BUILDING) || type.equals(TransactionConfidence.ConfidenceType.PENDING) && // we accept network tx without confirmations and numBroadcastPeers > 0 - /*confidence.getSource().equals(TransactionConfidence.Source.SELF) &&*/ + //confidence.getSource().equals(TransactionConfidence.Source.SELF) && // In regtest mode we expect to have only one peer, so we won't see transactions propagate. // TODO: The value 1 below dates from a time when transactions we broadcast *to* were // counted, set to 0 - (confidence.numBroadcastPeers() > 1 || tx.getParams() == RegTestParams.get()); + (confidence.numBroadcastPeers() > 1 || tx.getParams() == RegTestParams.get());*/ + + log.debug("numBroadcastPeers = " + confidence.numBroadcastPeers()); + // TODO at testnet we got confidence.numBroadcastPeers()=1 + return type.equals(TransactionConfidence.ConfidenceType.BUILDING) || + type.equals(TransactionConfidence.ConfidenceType.PENDING) && + (confidence.numBroadcastPeers() > 0 || tx.getParams() == RegTestParams.get()); } private static boolean isInBlockChain(Transaction tx) { diff --git a/src/main/java/io/bitsquare/btc/WalletFacade.java b/src/main/java/io/bitsquare/btc/WalletFacade.java index ef881adc26..32e87e8d5b 100644 --- a/src/main/java/io/bitsquare/btc/WalletFacade.java +++ b/src/main/java/io/bitsquare/btc/WalletFacade.java @@ -46,6 +46,7 @@ import org.bitcoinj.crypto.TransactionSignature; import org.bitcoinj.kits.WalletAppKit; import org.bitcoinj.params.MainNetParams; import org.bitcoinj.params.RegTestParams; +import org.bitcoinj.params.TestNet3Params; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.utils.Threading; @@ -54,13 +55,13 @@ import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.Service; import java.io.Serializable; import java.math.BigInteger; import java.util.ArrayList; -import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Optional; @@ -102,7 +103,7 @@ public class WalletFacade { private final FeePolicy feePolicy; private final CryptoFacade cryptoFacade; private final Persistence persistence; - private final List downloadListeners = new CopyOnWriteArrayList<>(); + // private final List downloadListeners = new CopyOnWriteArrayList<>(); private final List addressConfidenceListeners = new CopyOnWriteArrayList<>(); private final List txConfidenceListeners = new CopyOnWriteArrayList<>(); private final List balanceListeners = new CopyOnWriteArrayList<>(); @@ -132,7 +133,7 @@ public class WalletFacade { // Public Methods /////////////////////////////////////////////////////////////////////////////////////////// - public void initialize(StartupListener startupListener) { + public void initialize(org.bitcoinj.core.DownloadListener downloadListener, StartupListener startupListener) { // Tell bitcoinj to execute event handlers on the JavaFX UI thread. This keeps things simple and means // we cannot forget to switch threads when adding event handlers. Unfortunately, the DownloadListener // we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in @@ -164,7 +165,7 @@ public class WalletFacade { // Checkpoint files are made using the BuildCheckpoints tool and usually we have to download the // last months worth or more (takes a few seconds). try { - walletAppKit.setCheckpoints(getClass().getClassLoader().getResourceAsStream("wallet/checkpoints")); + walletAppKit.setCheckpoints(getClass().getResourceAsStream("wallet/checkpoints")); } catch (Exception e) { e.printStackTrace(); log.error(e.toString()); @@ -172,24 +173,40 @@ public class WalletFacade { // As an example! // walletAppKit.useTor(); } - walletAppKit.setDownloadListener(new BlockChainDownloadListener()) + else if (params == TestNet3Params.get()) { + walletAppKit.setCheckpoints(getClass().getResourceAsStream("wallet/checkpoints.testnet")); + //walletAppKit.useTor(); + } + walletAppKit.setDownloadListener(downloadListener) .setBlockingStartup(false) - .restoreWalletFromSeed(null) .setUserAgent("BitSquare", "0.1"); + + /* + // TODO restore from DeterministicSeed + if (seed != null) + walletAppKit.restoreWalletFromSeed(seed); + */ + walletAppKit.addListener(new Service.Listener() { + @Override + public void failed(Service.State from, Throwable failure) { + walletAppKit = null; + // TODO show error popup + //crashAlert(failure); + } + }, Threading.SAME_THREAD); walletAppKit.startAsync(); } private void initWallet() { wallet = walletAppKit.wallet(); - wallet.allowSpendingUnconfirmedTransactions(); //walletAppKit.peerGroup().setMaxConnections(11); - if (params == RegTestParams.get()) + /* if (params == RegTestParams.get()) walletAppKit.peerGroup().setMinBroadcastConnections(1); else - walletAppKit.peerGroup().setMinBroadcastConnections(3); + walletAppKit.peerGroup().setMinBroadcastConnections(3);*/ walletEventListener = new WalletEventListener() { @@ -266,14 +283,14 @@ public class WalletFacade { // Listener /////////////////////////////////////////////////////////////////////////////////////////// - public DownloadListener addDownloadListener(DownloadListener listener) { + /* public DownloadListener addDownloadListener(DownloadListener listener) { downloadListeners.add(listener); return listener; } public void removeDownloadListener(DownloadListener listener) { downloadListeners.remove(listener); - } + }*/ public AddressConfidenceListener addAddressConfidenceListener(AddressConfidenceListener listener) { addressConfidenceListeners.add(listener); @@ -1140,7 +1157,7 @@ public class WalletFacade { void downloadComplete(); } - private class BlockChainDownloadListener extends org.bitcoinj.core.DownloadListener { + /* private class BlockChainDownloadListener extends org.bitcoinj.core.DownloadListener { @Override protected void progress(double percent, int blocksSoFar, Date date) { super.progress(percent, blocksSoFar, date); @@ -1164,6 +1181,6 @@ public class WalletFacade { downloadListener.downloadComplete(); } } - } + }*/ } diff --git a/src/main/java/io/bitsquare/di/BitSquareModule.java b/src/main/java/io/bitsquare/di/BitSquareModule.java index 93849519be..8f5bebf79d 100644 --- a/src/main/java/io/bitsquare/di/BitSquareModule.java +++ b/src/main/java/io/bitsquare/di/BitSquareModule.java @@ -146,8 +146,8 @@ class NetworkParametersProvider implements Provider { // Set default // String networkType= WalletFacade.MAIN_NET; - // String networkType = WalletFacade.TEST_NET; - String networkType = WalletFacade.REG_TEST_NET; + String networkType = WalletFacade.TEST_NET; + // String networkType = WalletFacade.REG_TEST_NET; if (networkTypeFromConfig != null) networkType = networkTypeFromConfig; diff --git a/src/main/java/io/bitsquare/gui/main/MainModel.java b/src/main/java/io/bitsquare/gui/main/MainModel.java index 145835c57e..27a054dc3f 100644 --- a/src/main/java/io/bitsquare/gui/main/MainModel.java +++ b/src/main/java/io/bitsquare/gui/main/MainModel.java @@ -30,8 +30,13 @@ import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; import io.bitsquare.user.User; +import org.bitcoinj.core.DownloadListener; + import com.google.inject.Inject; +import java.util.Date; + +import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; @@ -60,8 +65,6 @@ class MainModel extends UIModel { final BooleanProperty backendInited = new SimpleBooleanProperty(); final DoubleProperty networkSyncProgress = new SimpleDoubleProperty(); - final BooleanProperty networkSyncComplete = new SimpleBooleanProperty(); - // final ObjectProperty balance = new SimpleObjectProperty<>(); final IntegerProperty numPendingTrades = new SimpleIntegerProperty(0); /////////////////////////////////////////////////////////////////////////////////////////// @@ -142,19 +145,24 @@ class MainModel extends UIModel { Profiler.printMsgWithTime("MainModel.initFacades"); - walletFacade.initialize(() -> { + DownloadListener downloadListener = new DownloadListener() { + @Override + protected void progress(double percent, int blocksLeft, Date date) { + super.progress(percent, blocksLeft, date); + Platform.runLater(() -> networkSyncProgress.set(percent / 100.0)); + } + + @Override + protected void doneDownload() { + super.doneDownload(); + Platform.runLater(() -> networkSyncProgress.set(1.0)); + } + }; + + walletFacade.initialize(downloadListener, () -> { walletFacadeInited = true; if (messageFacadeInited) onFacadesInitialised(); - - - /* walletFacade.addBalanceListener(new BalanceListener() { - @Override - public void onBalanceChanged(Coin balance) { - updateBalance(balance); - } - }); - updateBalance(walletFacade.getWalletBalance());*/ }); } @@ -187,19 +195,6 @@ class MainModel extends UIModel { /////////////////////////////////////////////////////////////////////////////////////////// private void onFacadesInitialised() { - // TODO Consider to use version sync notification pane from Mike Hearn - walletFacade.addDownloadListener(new WalletFacade.DownloadListener() { - @Override - public void progress(double percent) { - networkSyncProgress.set(percent); - } - - @Override - public void downloadComplete() { - networkSyncComplete.set(true); - } - }); - tradeManager.getPendingTrades().addListener((MapChangeListener) change -> updateNumPendingTrades()); updateNumPendingTrades(); @@ -211,7 +206,4 @@ class MainModel extends UIModel { numPendingTrades.set(tradeManager.getPendingTrades().size()); } - /* private void updateBalance(Coin balance) { - this.balance.set(balance); - }*/ } diff --git a/src/main/java/io/bitsquare/gui/main/MainPM.java b/src/main/java/io/bitsquare/gui/main/MainPM.java index fe4903d49a..cdc3f2c22c 100644 --- a/src/main/java/io/bitsquare/gui/main/MainPM.java +++ b/src/main/java/io/bitsquare/gui/main/MainPM.java @@ -24,9 +24,11 @@ import io.bitsquare.gui.util.BSFormatter; import com.google.inject.Inject; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -40,15 +42,14 @@ import org.slf4j.LoggerFactory; class MainPM extends PresentationModel { private static final Logger log = LoggerFactory.getLogger(MainPM.class); + private BSFormatter formatter; + final BooleanProperty backendInited = new SimpleBooleanProperty(); - // final StringProperty balance = new SimpleStringProperty(); final StringProperty bankAccountsComboBoxPrompt = new SimpleStringProperty(); final BooleanProperty bankAccountsComboBoxDisable = new SimpleBooleanProperty(); final StringProperty splashScreenInfoText = new SimpleStringProperty(); - final BooleanProperty networkSyncComplete = new SimpleBooleanProperty(); final IntegerProperty numPendingTrades = new SimpleIntegerProperty(); - private BSFormatter formatter; - + final DoubleProperty networkSyncProgress = new SimpleDoubleProperty(); /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -71,7 +72,7 @@ class MainPM extends PresentationModel { super.initialize(); backendInited.bind(model.backendInited); - networkSyncComplete.bind(model.networkSyncComplete); + networkSyncProgress.bind(model.networkSyncProgress); numPendingTrades.bind(model.numPendingTrades); model.networkSyncProgress.addListener((ov, oldValue, newValue) -> { @@ -84,9 +85,6 @@ class MainPM extends PresentationModel { }); - /*model.balance.addListener((ov, oldValue, newValue) -> balance.set(formatter.formatCoinWithCode - (newValue)));*/ - model.getBankAccounts().addListener((ListChangeListener) change -> { bankAccountsComboBoxDisable.set(change.getList().isEmpty()); bankAccountsComboBoxPrompt.set(change.getList().isEmpty() ? "No accounts" : ""); diff --git a/src/main/java/io/bitsquare/gui/main/MainViewCB.java b/src/main/java/io/bitsquare/gui/main/MainViewCB.java index bc361d666d..67b863ae55 100644 --- a/src/main/java/io/bitsquare/gui/main/MainViewCB.java +++ b/src/main/java/io/bitsquare/gui/main/MainViewCB.java @@ -52,6 +52,8 @@ import javafx.scene.paint.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import wallettemplate.controls.NotificationBarPane; + public class MainViewCB extends ViewCB { private static final Logger log = LoggerFactory.getLogger(MainViewCB.class); @@ -61,7 +63,7 @@ public class MainViewCB extends ViewCB { private final ToggleGroup navButtonsGroup = new ToggleGroup(); private BorderPane baseApplicationContainer; - private VBox baseOverlayContainer; + private StackPane baseOverlayContainer; private AnchorPane contentContainer; private HBox leftNavPane, rightNavPane; private NetworkSyncPane networkSyncPane; @@ -69,6 +71,7 @@ public class MainViewCB extends ViewCB { accountButton; private Pane ordersButtonButtonPane; private Label numPendingTradesLabel; + private NotificationBarPane notificationBarPane; /////////////////////////////////////////////////////////////////////////////////////////// @@ -166,10 +169,24 @@ public class MainViewCB extends ViewCB { private void startup() { baseApplicationContainer = getBaseApplicationContainer(); - baseOverlayContainer = getSplashScreen(); - ((StackPane) root).getChildren().addAll(baseApplicationContainer, baseOverlayContainer); + baseOverlayContainer = new StackPane(); - onBaseContainersCreated(); + // TODO remove dependency of NotificationBarPane with getSplashScreen (borderPane content) + notificationBarPane = new NotificationBarPane(getSplashScreen()); + baseOverlayContainer.getChildren().add(notificationBarPane); + + final NotificationBarPane.Item syncItem = notificationBarPane.pushItem("Synchronising with the Bitcoin network", + presentationModel.networkSyncProgress); + + presentationModel.networkSyncProgress.addListener((ov, oldValue, newValue) -> { + if ((double) newValue >= 1.0) { + log.debug("### networkSyncProgress " + newValue); + syncItem.cancel(); + onBaseContainersCreated(); + } + }); + + ((StackPane) root).getChildren().addAll(baseApplicationContainer, baseOverlayContainer); } private void onBaseContainersCreated() { @@ -284,7 +301,7 @@ public class MainViewCB extends ViewCB { return borderPane; } - private VBox getSplashScreen() { + private BorderPane getSplashScreen() { VBox vBox = new VBox(); vBox.setAlignment(Pos.CENTER); vBox.setSpacing(10); @@ -303,7 +320,9 @@ public class MainViewCB extends ViewCB { loadingLabel.textProperty().bind(presentationModel.splashScreenInfoText); vBox.getChildren().addAll(logo, subTitle, loadingLabel); - return vBox; + + BorderPane borderPane = new BorderPane(vBox); + return borderPane; } private AnchorPane getApplicationContainer() { @@ -333,15 +352,6 @@ public class MainViewCB extends ViewCB { AnchorPane.setLeftAnchor(networkSyncPane, 0d); AnchorPane.setBottomAnchor(networkSyncPane, 5d); - // TODO sometimes it keeps running... deactivate ti for the moment and replace it with the notification pane - // from Mike Hearn later - networkSyncPane.setVisible(false); - - presentationModel.networkSyncComplete.addListener((ov, old, newValue) -> { - if (newValue) - networkSyncPane.downloadComplete(); - }); - anchorPane.getChildren().addAll(leftNavPane, rightNavPane, contentContainer, networkSyncPane); return anchorPane; } @@ -361,8 +371,6 @@ public class MainViewCB extends ViewCB { msgButton = addNavButton(msgButtonHolder, "Messages", Navigation.Item.MSG); leftNavPane.getChildren().add(msgButtonHolder); - //addBalanceInfo(rightNavPane); - addBankAccountComboBox(rightNavPane); settingsButton = addNavButton(rightNavPane, "Preferences", Navigation.Item.SETTINGS); @@ -408,28 +416,6 @@ public class MainViewCB extends ViewCB { return toggleButton; } - /*private void addBalanceInfo(Pane parent) { - final TextField balanceTextField = new TextField(); - balanceTextField.setEditable(false); - balanceTextField.setPrefWidth(110); - balanceTextField.setId("nav-balance-label"); - balanceTextField.textProperty().bind(presentationModel.balance); - - - final Label titleLabel = new Label("Balance"); - titleLabel.setMouseTransparent(true); - titleLabel.setId("nav-button-label"); - balanceTextField.widthProperty().addListener((ov, o, n) -> - titleLabel.setLayoutX(((double) n - titleLabel.getWidth()) / 2)); - - final VBox vBox = new VBox(); - vBox.setPadding(new Insets(12, 5, 0, 0)); - vBox.setSpacing(2); - vBox.getChildren().setAll(balanceTextField, titleLabel); - vBox.setAlignment(Pos.CENTER); - parent.getChildren().add(vBox); - }*/ - private void addBankAccountComboBox(Pane parent) { final ComboBox comboBox = new ComboBox<>(presentationModel.getBankAccounts()); comboBox.setLayoutY(12); @@ -460,4 +446,4 @@ public class MainViewCB extends ViewCB { vBox.getChildren().setAll(comboBox, titleLabel); parent.getChildren().add(vBox); } -} +} \ No newline at end of file diff --git a/src/main/java/io/bitsquare/locale/BSResources.java b/src/main/java/io/bitsquare/locale/BSResources.java index 216b1b61c5..386c02e791 100644 --- a/src/main/java/io/bitsquare/locale/BSResources.java +++ b/src/main/java/io/bitsquare/locale/BSResources.java @@ -48,7 +48,7 @@ public class BSResources { try { return BSResources.getResourceBundle().getString(key); } catch (MissingResourceException e) { - log.warn("MissingResourceException for key: " + key); + log.warn("Missing resource for key: " + key); return key; } } diff --git a/src/main/java/wallettemplate/controls/NotificationBarPane.java b/src/main/java/wallettemplate/controls/NotificationBarPane.java new file mode 100644 index 0000000000..47024ab892 --- /dev/null +++ b/src/main/java/wallettemplate/controls/NotificationBarPane.java @@ -0,0 +1,142 @@ +package wallettemplate.controls; + + +import javax.annotation.Nullable; + +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ObservableDoubleValue; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.scene.*; +import javafx.scene.control.*; +import javafx.scene.layout.*; +import javafx.util.Duration; + +import wallettemplate.utils.GuiUtils; +import wallettemplate.utils.easing.EasingMode; +import wallettemplate.utils.easing.ElasticInterpolator; + +/** + * Wraps the given Node in a BorderPane and allows a thin bar to slide in from the bottom or top, squeezing the content + * node. The API allows different "items" to be added/removed and they will be displayed one at a time, fading between + * them when the topmost is removed. Each item is meant to be used for e.g. a background task and can contain a button + * and/or a progress bar. + */ +public class NotificationBarPane extends BorderPane { + public static final Duration ANIM_IN_DURATION = GuiUtils.UI_ANIMATION_TIME.multiply(2); + public static final Duration ANIM_OUT_DURATION = GuiUtils.UI_ANIMATION_TIME; + + private HBox bar; + private Label label; + private double barHeight; + private ProgressBar progressBar; + + public class Item { + public final SimpleStringProperty label; + @Nullable public final ObservableDoubleValue progress; + + public Item(String label, @Nullable ObservableDoubleValue progress) { + this.label = new SimpleStringProperty(label); + this.progress = progress; + } + + public void cancel() { + items.remove(this); + } + } + + public final ObservableList items; + + public NotificationBarPane(Node content) { + super(content); + progressBar = new ProgressBar(); + label = new Label("infobar!"); + bar = new HBox(label); + bar.setMinHeight(0.0); + bar.getStyleClass().add("info-bar"); + bar.setFillHeight(true); + setBottom(bar); + // Figure out the height of the bar based on the CSS. Must wait until after we've been added to the parent node. + sceneProperty().addListener(o -> { + if (getParent() == null) return; + getParent().applyCss(); + getParent().layout(); + barHeight = bar.getHeight(); + bar.setPrefHeight(0.0); + }); + items = FXCollections.observableArrayList(); + items.addListener((ListChangeListener) change -> { + config(); + showOrHide(); + }); + } + + private void config() { + if (items.isEmpty()) return; + Item item = items.get(0); + + bar.getChildren().clear(); + label.textProperty().bind(item.label); + label.setMaxWidth(Double.MAX_VALUE); + HBox.setHgrow(label, Priority.ALWAYS); + bar.getChildren().add(label); + if (item.progress != null) { + progressBar.setMinWidth(200); + progressBar.progressProperty().bind(item.progress); + bar.getChildren().add(progressBar); + } + } + + private void showOrHide() { + if (items.isEmpty()) + animateOut(); + else + animateIn(); + } + + public boolean isShowing() { + return bar.getPrefHeight() > 0; + } + + private void animateIn() { + animate(barHeight); + } + + private void animateOut() { + animate(0.0); + } + + private Timeline timeline; + + protected void animate(Number target) { + if (timeline != null) { + timeline.stop(); + timeline = null; + } + Duration duration; + Interpolator interpolator; + if (target.intValue() > 0) { + interpolator = new ElasticInterpolator(EasingMode.EASE_OUT, 1, 2); + duration = ANIM_IN_DURATION; + } + else { + interpolator = Interpolator.EASE_OUT; + duration = ANIM_OUT_DURATION; + } + KeyFrame kf = new KeyFrame(duration, new KeyValue(bar.prefHeightProperty(), target, interpolator)); + timeline = new Timeline(kf); + timeline.setOnFinished(x -> timeline = null); + timeline.play(); + } + + public Item pushItem(String string, @Nullable ObservableDoubleValue progress) { + Item i = new Item(string, progress); + items.add(i); + return i; + } +} \ No newline at end of file diff --git a/src/main/java/wallettemplate/utils/GuiUtils.java b/src/main/java/wallettemplate/utils/GuiUtils.java new file mode 100644 index 0000000000..1aaed136ff --- /dev/null +++ b/src/main/java/wallettemplate/utils/GuiUtils.java @@ -0,0 +1,166 @@ +package wallettemplate.utils; + +import javafx.animation.Animation; +import javafx.animation.FadeTransition; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.ScaleTransition; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.scene.*; +import javafx.scene.effect.*; +import javafx.scene.layout.*; +import javafx.util.Duration; + +import static com.google.common.base.Preconditions.checkState; + +public class GuiUtils { + /*public static void runAlert(BiConsumer setup) { + try { + // JavaFX2 doesn't actually have a standard alert template. Instead the Scene Builder app will create FXML + // files for an alert window for you, and then you customise it as you see fit. I guess it makes sense in + // an odd sort of way. + Stage dialogStage = new Stage(); + dialogStage.initModality(Modality.APPLICATION_MODAL); + FXMLLoader loader = new FXMLLoader(GuiUtils.class.getResource("alert.fxml")); + Pane pane = loader.load(); + AlertWindowController controller = loader.getController(); + setup.accept(dialogStage, controller); + dialogStage.setScene(new Scene(pane)); + dialogStage.showAndWait(); + } catch (IOException e) { + // We crashed whilst trying to show the alert dialog (this should never happen). Give up! + throw new RuntimeException(e); + } + }*/ + + /* public static void crashAlert(Throwable t) { + t.printStackTrace(); + Throwable rootCause = Throwables.getRootCause(t); + Runnable r = () -> { + runAlert((stage, controller) -> controller.crashAlert(stage, rootCause.toString())); + Platform.exit(); + }; + if (Platform.isFxApplicationThread()) + r.run(); + else + Platform.runLater(r); + }*/ + + /** + * Show a GUI alert box for any unhandled exceptions that propagate out of this thread. + */ + /*public static void handleCrashesOnThisThread() { + Thread.currentThread().setUncaughtExceptionHandler((thread, exception) -> { + GuiUtils.crashAlert(Throwables.getRootCause(exception)); + }); + } + + public static void informationalAlert(String message, String details, Object... args) { + String formattedDetails = String.format(details, args); + Runnable r = () -> runAlert((stage, controller) -> controller.informational(stage, message, formattedDetails)); + if (Platform.isFxApplicationThread()) + r.run(); + else + Platform.runLater(r); + }*/ + + public static final int UI_ANIMATION_TIME_MSEC = 600; + public static final Duration UI_ANIMATION_TIME = Duration.millis(UI_ANIMATION_TIME_MSEC); + + public static Animation fadeIn(Node ui) { + return fadeIn(ui, 0); + } + + public static Animation fadeIn(Node ui, int delayMillis) { + ui.setCache(true); + FadeTransition ft = new FadeTransition(Duration.millis(UI_ANIMATION_TIME_MSEC), ui); + ft.setFromValue(0.0); + ft.setToValue(1.0); + ft.setOnFinished(ev -> ui.setCache(false)); + ft.setDelay(Duration.millis(delayMillis)); + ft.play(); + return ft; + } + + public static Animation fadeOut(Node ui) { + FadeTransition ft = new FadeTransition(Duration.millis(UI_ANIMATION_TIME_MSEC), ui); + ft.setFromValue(ui.getOpacity()); + ft.setToValue(0.0); + ft.play(); + return ft; + } + + public static Animation fadeOutAndRemove(Pane parentPane, Node... nodes) { + Animation animation = fadeOut(nodes[0]); + animation.setOnFinished(actionEvent -> parentPane.getChildren().removeAll(nodes)); + return animation; + } + + public static Animation fadeOutAndRemove(Duration duration, Pane parentPane, Node... nodes) { + nodes[0].setCache(true); + FadeTransition ft = new FadeTransition(duration, nodes[0]); + ft.setFromValue(nodes[0].getOpacity()); + ft.setToValue(0.0); + ft.setOnFinished(actionEvent -> parentPane.getChildren().removeAll(nodes)); + ft.play(); + return ft; + } + + public static void blurOut(Node node) { + GaussianBlur blur = new GaussianBlur(0.0); + node.setEffect(blur); + Timeline timeline = new Timeline(); + KeyValue kv = new KeyValue(blur.radiusProperty(), 10.0); + KeyFrame kf = new KeyFrame(Duration.millis(UI_ANIMATION_TIME_MSEC), kv); + timeline.getKeyFrames().add(kf); + timeline.play(); + } + + public static void blurIn(Node node) { + GaussianBlur blur = (GaussianBlur) node.getEffect(); + Timeline timeline = new Timeline(); + KeyValue kv = new KeyValue(blur.radiusProperty(), 0.0); + KeyFrame kf = new KeyFrame(Duration.millis(UI_ANIMATION_TIME_MSEC), kv); + timeline.getKeyFrames().add(kf); + timeline.setOnFinished(actionEvent -> node.setEffect(null)); + timeline.play(); + } + + public static ScaleTransition zoomIn(Node node) { + return zoomIn(node, 0); + } + + public static ScaleTransition zoomIn(Node node, int delayMillis) { + return scaleFromTo(node, 0.95, 1.0, delayMillis); + } + + public static ScaleTransition explodeOut(Node node) { + return scaleFromTo(node, 1.0, 1.05, 0); + } + + private static ScaleTransition scaleFromTo(Node node, double from, double to, int delayMillis) { + ScaleTransition scale = new ScaleTransition(Duration.millis(UI_ANIMATION_TIME_MSEC / 2), node); + scale.setFromX(from); + scale.setFromY(from); + scale.setToX(to); + scale.setToY(to); + scale.setDelay(Duration.millis(delayMillis)); + scale.play(); + return scale; + } + + /** + * A useful helper for development purposes. Used as a switch for loading files from local disk, allowing live + * editing whilst the app runs without rebuilds. + */ + /* public static URL getResource(String name) { + if (false) + return unchecked(() -> new URL("file:///your/path/here/src/main/wallettemplate/" + name)); + else + return MainController.class.getResource(name); + }*/ + public static void checkGuiThread() { + checkState(Platform.isFxApplicationThread()); + } +} diff --git a/src/main/java/wallettemplate/utils/WTUtils.java b/src/main/java/wallettemplate/utils/WTUtils.java new file mode 100644 index 0000000000..2293f926d8 --- /dev/null +++ b/src/main/java/wallettemplate/utils/WTUtils.java @@ -0,0 +1,87 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package wallettemplate.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Some generic utilities to make Java a bit less annoying. + */ +public class WTUtils { + private static final Logger log = LoggerFactory.getLogger(WTUtils.class); + + public interface UncheckedRun { + public T run() throws Throwable; + } + + public interface UncheckedRunnable { + public void run() throws Throwable; + } + + public static T unchecked(UncheckedRun run) { + try { + return run.run(); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + public static void uncheck(UncheckedRunnable run) { + try { + run.run(); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + public static void ignoreAndLog(UncheckedRunnable runnable) { + try { + runnable.run(); + } catch (Throwable t) { + log.error("Ignoring error", t); + } + } + + public static T ignoredAndLogged(UncheckedRun runnable) { + try { + return runnable.run(); + } catch (Throwable t) { + log.error("Ignoring error", t); + return null; + } + } + + public static boolean didThrow(UncheckedRun run) { + try { + run.run(); + return false; + } catch (Throwable throwable) { + return true; + } + } + + public static boolean didThrow(UncheckedRunnable run) { + try { + run.run(); + return false; + } catch (Throwable throwable) { + return true; + } + } +} diff --git a/src/main/java/wallettemplate/utils/easing/EasingInterpolator.java b/src/main/java/wallettemplate/utils/easing/EasingInterpolator.java new file mode 100644 index 0000000000..5c2adef59c --- /dev/null +++ b/src/main/java/wallettemplate/utils/easing/EasingInterpolator.java @@ -0,0 +1,134 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +/* + * The MIT License (MIT) + * + * Copyright (c) 2013, Christian Schudt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package wallettemplate.utils.easing; + +import javafx.animation.Interpolator; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +/** + * The abstract base class for all easing interpolators. + * + * @author Christian Schudt + */ +public abstract class EasingInterpolator extends Interpolator { + + /** + * The easing mode. + */ + private ObjectProperty easingMode = new SimpleObjectProperty<>(EasingMode.EASE_OUT); + + /** + * Constructs the interpolator with a specific easing mode. + * + * @param easingMode The easing mode. + */ + public EasingInterpolator(EasingMode easingMode) { + this.easingMode.set(easingMode); + } + + /** + * The easing mode property. + * + * @return The property. + * @see #getEasingMode() + * @see #setEasingMode(EasingMode) + */ + public ObjectProperty easingModeProperty() { + return easingMode; + } + + /** + * Gets the easing mode. + * + * @return The easing mode. + * @see #easingModeProperty() + */ + public EasingMode getEasingMode() { + return easingMode.get(); + } + + /** + * Sets the easing mode. + * + * @param easingMode The easing mode. + * @see #easingModeProperty() + */ + public void setEasingMode(EasingMode easingMode) { + this.easingMode.set(easingMode); + } + + /** + * Defines the base curve for the interpolator. + * The base curve is then transformed into an easing-in, easing-out easing-both curve. + * + * @param v The normalized value/time/progress of the interpolation (between 0 and 1). + * @return The resulting value of the function, should return a value between 0 and 1. + * @see javafx.animation.Interpolator#curve(double) + */ + protected abstract double baseCurve(final double v); + + /** + * Curves the function depending on the easing mode. + * + * @param v The normalized value (between 0 and 1). + * @return The resulting value of the function. + */ + @Override + protected final double curve(final double v) { + switch (easingMode.get()) { + case EASE_IN: + return baseCurve(v); + case EASE_OUT: + return 1 - baseCurve(1 - v); + case EASE_BOTH: + if (v <= 0.5) { + return baseCurve(2 * v) / 2; + } + else { + return (2 - baseCurve(2 * (1 - v))) / 2; + } + + } + return baseCurve(v); + } +} diff --git a/src/main/java/wallettemplate/utils/easing/EasingMode.java b/src/main/java/wallettemplate/utils/easing/EasingMode.java new file mode 100644 index 0000000000..46b29bbe12 --- /dev/null +++ b/src/main/java/wallettemplate/utils/easing/EasingMode.java @@ -0,0 +1,12 @@ +package wallettemplate.utils.easing; + +/** + * Defines the three easing modes, ease-in, ease-out and ease-both. + * + * @author Christian Schudt + */ +public enum EasingMode { + EASE_IN, + EASE_OUT, + EASE_BOTH +} diff --git a/src/main/java/wallettemplate/utils/easing/ElasticInterpolator.java b/src/main/java/wallettemplate/utils/easing/ElasticInterpolator.java new file mode 100644 index 0000000000..f627f217c0 --- /dev/null +++ b/src/main/java/wallettemplate/utils/easing/ElasticInterpolator.java @@ -0,0 +1,180 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2013, Christian Schudt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package wallettemplate.utils.easing; + +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleDoubleProperty; + +/** + * This interpolator simulates an elastic behavior. + *

+ * The following curve illustrates the interpolation. + *

+ * + * + * x + * + * t + * + * + *

+ * The math in this class is taken from + * http://www.robertpenner.com/easing/. + * + * @author Christian Schudt + */ +public class ElasticInterpolator extends EasingInterpolator { + + /** + * The amplitude. + */ + private DoubleProperty amplitude = new SimpleDoubleProperty(this, "amplitude", 1); + + /** + * The number of oscillations. + */ + private DoubleProperty oscillations = new SimpleDoubleProperty(this, "oscillations", 3); + + /** + * Default constructor. Initializes the interpolator with ease out mode. + */ + public ElasticInterpolator() { + this(EasingMode.EASE_OUT); + } + + /** + * Constructs the interpolator with a specific easing mode. + * + * @param easingMode The easing mode. + */ + public ElasticInterpolator(EasingMode easingMode) { + super(easingMode); + } + + /** + * Sets the easing mode. + * + * @param easingMode The easing mode. + * @see #easingModeProperty() + */ + public ElasticInterpolator(EasingMode easingMode, double amplitude, double oscillations) { + super(easingMode); + this.amplitude.set(amplitude); + this.oscillations.set(oscillations); + } + + /** + * The oscillations property. Defines number of oscillations. + * + * @return The property. + * @see #getOscillations() + * @see #setOscillations(double) + */ + public DoubleProperty oscillationsProperty() { + return oscillations; + } + + /** + * The amplitude. The minimum value is 1. If this value is < 1 it will be set to 1 during animation. + * + * @return The property. + * @see #getAmplitude() + * @see #setAmplitude(double) + */ + public DoubleProperty amplitudeProperty() { + return amplitude; + } + + /** + * Gets the amplitude. + * + * @return The amplitude. + * @see #amplitudeProperty() + */ + public double getAmplitude() { + return amplitude.get(); + } + + /** + * Sets the amplitude. + * + * @param amplitude The amplitude. + * @see #amplitudeProperty() + */ + public void setAmplitude(final double amplitude) { + this.amplitude.set(amplitude); + } + + /** + * Gets the number of oscillations. + * + * @return The oscillations. + * @see #oscillationsProperty() + */ + public double getOscillations() { + return oscillations.get(); + } + + /** + * Sets the number of oscillations. + * + * @param oscillations The oscillations. + * @see #oscillationsProperty() + */ + public void setOscillations(final double oscillations) { + this.oscillations.set(oscillations); + } + + @Override + protected double baseCurve(double v) { + if (v == 0) { + return 0; + } + if (v == 1) { + return 1; + } + double p = 1.0 / oscillations.get(); + double a = amplitude.get(); + double s; + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } + else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return -(a * Math.pow(2, 10 * (v -= 1)) * Math.sin((v - s) * (2 * Math.PI) / p)); + } +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 75ed2bdde6..ddc54c46ec 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -25,7 +25,7 @@ - + @@ -36,6 +36,10 @@ + + + +