/*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.offer.createoffer;
import de.jensd.fx.fontawesome.AwesomeDude;
import de.jensd.fx.fontawesome.AwesomeIcon;
import io.bitsquare.app.DevFlags;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.Tuple2;
import io.bitsquare.common.util.Tuple3;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.*;
import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.account.AccountView;
import io.bitsquare.gui.main.account.content.arbitratorselection.ArbitratorSelectionView;
import io.bitsquare.gui.main.account.settings.AccountSettingsView;
import io.bitsquare.gui.main.funds.FundsView;
import io.bitsquare.gui.main.funds.withdrawal.WithdrawalView;
import io.bitsquare.gui.main.offer.OfferView;
import io.bitsquare.gui.main.overlays.popups.Popup;
import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow;
import io.bitsquare.gui.main.overlays.windows.QRCodeWindow;
import io.bitsquare.gui.main.portfolio.PortfolioView;
import io.bitsquare.gui.main.portfolio.openoffer.OpenOffersView;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.FormBuilder;
import io.bitsquare.gui.util.GUIUtil;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.locale.BSResources;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.payment.PaymentAccount;
import io.bitsquare.payment.PaymentMethod;
import io.bitsquare.trade.offer.Offer;
import io.bitsquare.user.Preferences;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.*;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.scene.text.Font;
import javafx.stage.Window;
import javafx.util.StringConverter;
import net.glxn.qrgen.QRCode;
import net.glxn.qrgen.image.ImageType;
import org.bitcoinj.uri.BitcoinURI;
import org.controlsfx.control.PopOver;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
import java.io.ByteArrayInputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static io.bitsquare.gui.util.FormBuilder.*;
import static javafx.beans.binding.Bindings.createStringBinding;
@FxmlView
public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateOfferViewModel> {
private final Navigation navigation;
private final OfferDetailsWindow offerDetailsWindow;
private ScrollPane scrollPane;
private GridPane gridPane;
private ImageView imageView;
private AddressTextField addressTextField;
private BalanceTextField balanceTextField;
private TitledGroupBg payFundsPane;
private BusyAnimation waitingForFundsBusyAnimation;
private Button nextButton, cancelButton1, cancelButton2, fundFromSavingsWalletButton, fundFromExternalWalletButton, placeOfferButton;
private InputTextField amountTextField, minAmountTextField, fixedPriceTextField, marketBasedPriceTextField, volumeTextField;
private TextField currencyTextField;
private Label directionLabel, amountDescriptionLabel, addressLabel, balanceLabel,
totalToPayLabel, totalToPayInfoIconLabel, amountBtcLabel, priceCurrencyLabel,
volumeCurrencyLabel, minAmountBtcLabel, priceDescriptionLabel,
volumeDescriptionLabel, currencyTextFieldLabel,
currencyComboBoxLabel, waitingForFundsLabel, marketBasedPriceLabel;
private TextFieldWithCopyIcon totalToPayTextField;
private ComboBox<PaymentAccount> paymentAccountsComboBox;
private ComboBox<TradeCurrency> currencyComboBox;
private PopOver totalToPayInfoPopover;
private ToggleButton fixedPriceButton, useMarketBasedPriceButton;
private OfferView.CloseHandler closeHandler;
private ChangeListener<Boolean> amountFocusedListener;
private ChangeListener<Boolean> minAmountFocusedListener;
private ChangeListener<Boolean> priceFocusedListener, priceAsPercentageFocusedListener;
private ChangeListener<Boolean> volumeFocusedListener;
private ChangeListener<String> errorMessageListener;
private ChangeListener<Boolean> placeOfferCompletedListener;
// private ChangeListener<Coin> feeFromFundingTxListener;
private EventHandler<ActionEvent> paymentAccountsComboBoxSelectionHandler;
private EventHandler<ActionEvent> currencyComboBoxSelectionHandler;
private int gridRow = 0;
private final Preferences preferences;
private BSFormatter formatter;
private ChangeListener<String> tradeCurrencyCodeListener;
private ImageView qrCodeImageView;
private HBox fundingHBox;
private Subscription isWaitingForFundsSubscription;
private Subscription cancelButton2StyleSubscription;
private Subscription balanceSubscription;
private List<Node> editOfferElements = new ArrayList<>();
private boolean isActivated;
private Label xLabel;
private boolean clearXchangeWarningDisplayed;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private CreateOfferView(CreateOfferViewModel model, Navigation navigation,
OfferDetailsWindow offerDetailsWindow, Preferences preferences, BSFormatter formatter) {
super(model);
this.navigation = navigation;
this.offerDetailsWindow = offerDetailsWindow;
this.preferences = preferences;
this.formatter = formatter;
}
@Override
protected void initialize() {
addScrollPane();
addGridPane();
addPaymentGroup();
addAmountPriceGroup();
addFundingGroup();
createListeners();
balanceTextField.setFormatter(model.getFormatter());
paymentAccountsComboBox.setConverter(new StringConverter<PaymentAccount>() {
@Override
public String toString(PaymentAccount paymentAccount) {
return paymentAccount.getAccountName() + " (" + paymentAccount.getSingleTradeCurrency().getCode() + ", " +
BSResources.get(paymentAccount.getPaymentMethod().getId()) + ")";
}
@Override
public PaymentAccount fromString(String s) {
return null;
}
});
}
@Override
protected void activate() {
if (model.dataModel.isTabSelected)
doActivate();
}
private void doActivate() {
if (!isActivated) {
isActivated = true;
currencyComboBox.setPrefWidth(250);
paymentAccountsComboBox.setPrefWidth(250);
addBindings();
addListeners();
addSubscriptions();
if (waitingForFundsBusyAnimation != null)
waitingForFundsBusyAnimation.play();
useMarketBasedPriceButton.setSelected(model.dataModel.useMarketBasedPrice.get());
fixedPriceButton.setSelected(!model.dataModel.useMarketBasedPrice.get());
directionLabel.setText(model.getDirectionLabel());
amountDescriptionLabel.setText(model.getAmountDescription());
addressTextField.setAddress(model.getAddressAsString());
addressTextField.setPaymentLabel(model.getPaymentLabel());
paymentAccountsComboBox.setItems(model.dataModel.getPaymentAccounts());
paymentAccountsComboBox.getSelectionModel().select(model.getPaymentAccount());
onPaymentAccountsComboBoxSelected();
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
// if (DevFlags.STRESS_TEST_MODE)
// UserThread.runAfter(this::onShowPayFundsScreen, 200, TimeUnit.MILLISECONDS);
}
}
@Override
protected void deactivate() {
if (isActivated) {
isActivated = false;
removeBindings();
removeListeners();
removeSubscriptions();
if (waitingForFundsBusyAnimation != null)
waitingForFundsBusyAnimation.stop();
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void onTabSelected(boolean isSelected) {
if (isSelected && !model.dataModel.isTabSelected)
doActivate();
else
deactivate();
isActivated = isSelected;
model.dataModel.onTabSelected(isSelected);
}
public void initWithData(Offer.Direction direction, TradeCurrency tradeCurrency) {
boolean result = model.initWithData(direction, tradeCurrency);
if (!result)
new Popup().warning("You don't have a trading account set up.").onClose(this::close).show();
if (direction == Offer.Direction.BUY) {
imageView.setId("image-buy-large");
placeOfferButton.setId("buy-button-big");
placeOfferButton.setText("Review offer to buy bitcoin");
nextButton.setId("buy-button");
} else {
imageView.setId("image-sell-large");
// only needed for sell
totalToPayTextField.setPromptText(BSResources.get("createOffer.fundsBox.totalsNeeded.prompt"));
placeOfferButton.setId("sell-button-big");
placeOfferButton.setText("Review offer to sell bitcoin");
nextButton.setId("sell-button");
}
}
// called form parent as the view does not get notified when the tab is closed
public void onClose() {
// we use model.placeOfferCompleted to not react on close which was triggered by a successful placeOffer
if (model.dataModel.balance.get().isPositive() && !model.placeOfferCompleted.get()) {
model.dataModel.swapTradeToSavings();
new Popup().information("You had already funded that offer.\n" +
"Your funds have been moved to your local Bitsquare wallet and are available for " +
"withdrawal in the \"Funds/Available for withdrawal\" screen.")
.actionButtonText("Go to \"Funds/Available for withdrawal\"")
.onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class))
.show();
}
}
public void setCloseHandler(OfferView.CloseHandler closeHandler) {
this.closeHandler = closeHandler;
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions
///////////////////////////////////////////////////////////////////////////////////////////
private void onPlaceOffer() {
if (model.isBootstrapped()) {
if (model.hasAcceptedArbitrators()) {
Offer offer = model.createAndGetOffer();
if (!DevFlags.DEV_MODE)
offerDetailsWindow.onPlaceOffer(() ->
model.onPlaceOffer(offer, () ->
offerDetailsWindow.hide()))
.show(offer);
else
model.onPlaceOffer(offer, () -> {
});
} else {
new Popup().warning("You have no arbitrator selected.\n" +
"You need to select at least one arbitrator.")
.actionButtonText("Go to \"Arbitrator selection\"")
.onAction(() -> navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class, ArbitratorSelectionView.class))
.show();
}
} else {
new Popup().information("You need to wait until you are fully connected to the network.\n" +
"That might take up to about 2 minutes at startup.").show();
}
}
private void onShowPayFundsScreen() {
model.onShowPayFundsScreen();
editOfferElements.stream().forEach(node -> {
node.setMouseTransparent(true);
node.setFocusTraversable(false);
});
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
if (!DevFlags.DEV_MODE) {
String key = "securityDepositInfo";
new Popup().backgroundInfo("To ensure that both traders follow the trade protocol they need to pay a security deposit.\n\n" +
"The deposit will stay in your local trading wallet until the offer gets accepted by another trader.\n" +
"It will be refunded to you after the trade has successfully completed.\n\n" +
"Please note that you need to keep you application running if you have an open offer.\n" +
"When another trader wants to take your offer it requires that your application is online and able to react.\n" +
"Be sure that you have standby mode deactivated as that would disconnect your client from the network (standby of the monitor is not a problem).")
.actionButtonText("Visit FAQ web page")
.onAction(() -> GUIUtil.openWebPage("https://bitsquare.io/faq#6"))
.closeButtonText("I understand")
.dontShowAgainId(key, preferences)
.show();
key = "createOfferFundWalletInfo";
String tradeAmountText = model.isSellOffer() ? "- Trade amount: " + model.tradeAmount.get() + "\n" : "";
new Popup().headLine("Fund your offer").instruction("You need to deposit " +
model.totalToPay.get() + " to this offer.\n\n" +
"Those funds are reserved in your local wallet and will get locked into the Multisig " +
"deposit address once someone takes your offer.\n\n" +
"The amount is the sum of:\n" +
tradeAmountText +
"- Security deposit: " + model.getSecurityDeposit() + "\n" +
"- Trading fee: " + model.getOfferFee() + "\n" +
"- Bitcoin mining fee: " + model.getNetworkFee() + "\n\n" +
"You can choose between two options when funding your trade:\n" +
"- Use your Bitsquare wallet (convenient, but transactions may be linkable) OR\n" +
"- Transfer from an external wallet (potentially more private)\n\n" +
"You will see all funding options and details after closing this popup.")
.dontShowAgainId(key, preferences)
.show();
}
nextButton.setVisible(false);
nextButton.setManaged(false);
cancelButton1.setVisible(false);
cancelButton1.setManaged(false);
cancelButton1.setOnAction(null);
waitingForFundsBusyAnimation.play();
payFundsPane.setVisible(true);
totalToPayLabel.setVisible(true);
totalToPayInfoIconLabel.setVisible(true);
totalToPayTextField.setVisible(true);
addressLabel.setVisible(true);
addressTextField.setVisible(true);
qrCodeImageView.setVisible(true);
balanceLabel.setVisible(true);
balanceTextField.setVisible(true);
cancelButton2.setVisible(true);
//root.requestFocus();
setupTotalToPayInfoIconLabel();
final byte[] imageBytes = QRCode
.from(getBitcoinURI())
.withSize(98, 98) // code has 41 elements 8 px is border with 98 we get double scale and min. border
.to(ImageType.PNG)
.stream()
.toByteArray();
Image qrImage = new Image(new ByteArrayInputStream(imageBytes));
qrCodeImageView.setImage(qrImage);
}
private void maybeShowClearXchangeWarning(PaymentAccount paymentAccount) {
if (paymentAccount.getPaymentMethod().getId().equals(PaymentMethod.CLEAR_X_CHANGE_ID) &&
!clearXchangeWarningDisplayed) {
clearXchangeWarningDisplayed = true;
UserThread.runAfter(() -> GUIUtil.showClearXchangeWarning(paymentAccount.getPaymentMethod(), preferences),
500, TimeUnit.MILLISECONDS);
}
}
private void onPaymentAccountsComboBoxSelected() {
PaymentAccount paymentAccount = paymentAccountsComboBox.getSelectionModel().getSelectedItem();
maybeShowClearXchangeWarning(paymentAccount);
if (paymentAccount != null) {
currencyComboBox.setVisible(paymentAccount.hasMultipleCurrencies());
if (paymentAccount.hasMultipleCurrencies()) {
currencyComboBox.setItems(FXCollections.observableArrayList(paymentAccount.getTradeCurrencies()));
// we select combobox following the user currency, if user currency not available in account, we select first
TradeCurrency tradeCurrency = model.getTradeCurrency();
if (paymentAccount.getTradeCurrencies().contains(tradeCurrency))
currencyComboBox.getSelectionModel().select(tradeCurrency);
else
currencyComboBox.getSelectionModel().select(paymentAccount.getTradeCurrencies().get(0));
model.onPaymentAccountSelected(paymentAccount);
} else {
currencyTextField.setText(paymentAccount.getSingleTradeCurrency().getNameAndCode());
model.onPaymentAccountSelected(paymentAccount);
model.onCurrencySelected(paymentAccount.getSingleTradeCurrency());
}
} else {
currencyComboBox.setVisible(false);
currencyTextField.setText("");
}
}
private void onCurrencyComboBoxSelected() {
model.onCurrencySelected(currencyComboBox.getSelectionModel().getSelectedItem());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Navigation
///////////////////////////////////////////////////////////////////////////////////////////
private void close() {
if (closeHandler != null)
closeHandler.close();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Bindings, Listeners
///////////////////////////////////////////////////////////////////////////////////////////
private void addBindings() {
amountBtcLabel.textProperty().bind(model.btcCode);
priceCurrencyLabel.textProperty().bind(createStringBinding(() -> formatter.getCounterCurrency(model.tradeCurrencyCode.get()), model.btcCode, model.tradeCurrencyCode));
fixedPriceTextField.disableProperty().bind(model.dataModel.useMarketBasedPrice);
priceCurrencyLabel.disableProperty().bind(model.dataModel.useMarketBasedPrice);
marketBasedPriceTextField.disableProperty().bind(model.dataModel.useMarketBasedPrice.not());
marketBasedPriceLabel.disableProperty().bind(model.dataModel.useMarketBasedPrice.not());
marketBasedPriceLabel.prefWidthProperty().bind(priceCurrencyLabel.widthProperty());
volumeCurrencyLabel.textProperty().bind(model.tradeCurrencyCode);
minAmountBtcLabel.textProperty().bind(model.btcCode);
priceDescriptionLabel.textProperty().bind(createStringBinding(() -> {
// String currencyCode = model.tradeCurrencyCode.get();
return formatter.getPriceWithCurrencyCode(model.tradeCurrencyCode.get());
//BSResources.get("createOffer.amountPriceBox.priceDescriptionFiat", currencyCode);
/* return CurrencyUtil.isCryptoCurrency(currencyCode) ?
BSResources.get("createOffer.amountPriceBox.priceDescriptionAltcoin", currencyCode) :
BSResources.get("createOffer.amountPriceBox.priceDescriptionFiat", currencyCode);*/
}, model.tradeCurrencyCode));
//xLabel.textProperty().bind(createStringBinding(() -> CurrencyUtil.isCryptoCurrency(model.tradeCurrencyCode.get()) ? "/" : "x", model.tradeCurrencyCode));
xLabel.setText("x");
volumeDescriptionLabel.textProperty().bind(createStringBinding(model.volumeDescriptionLabel::get, model.tradeCurrencyCode, model.volumeDescriptionLabel));
amountTextField.textProperty().bindBidirectional(model.amount);
minAmountTextField.textProperty().bindBidirectional(model.minAmount);
fixedPriceTextField.textProperty().bindBidirectional(model.price);
marketBasedPriceTextField.textProperty().bindBidirectional(model.marketPriceMargin);
volumeTextField.textProperty().bindBidirectional(model.volume);
volumeTextField.promptTextProperty().bind(model.volumePromptLabel);
totalToPayTextField.textProperty().bind(model.totalToPay);
addressTextField.amountAsCoinProperty().bind(model.dataModel.missingCoin);
// Validation
amountTextField.validationResultProperty().bind(model.amountValidationResult);
minAmountTextField.validationResultProperty().bind(model.minAmountValidationResult);
fixedPriceTextField.validationResultProperty().bind(model.priceValidationResult);
volumeTextField.validationResultProperty().bind(model.volumeValidationResult);
// funding
fundingHBox.visibleProperty().bind(model.dataModel.isWalletFunded.not().and(model.showPayFundsScreenDisplayed));
fundingHBox.managedProperty().bind(model.dataModel.isWalletFunded.not().and(model.showPayFundsScreenDisplayed));
waitingForFundsLabel.textProperty().bind(model.waitingForFundsText);
placeOfferButton.visibleProperty().bind(model.dataModel.isWalletFunded.and(model.showPayFundsScreenDisplayed));
placeOfferButton.managedProperty().bind(model.dataModel.isWalletFunded.and(model.showPayFundsScreenDisplayed));
placeOfferButton.disableProperty().bind(model.isPlaceOfferButtonDisabled);
cancelButton2.disableProperty().bind(model.cancelButtonDisabled);
// trading account
currencyComboBox.prefWidthProperty().bind(paymentAccountsComboBox.widthProperty());
currencyComboBox.managedProperty().bind(currencyComboBox.visibleProperty());
currencyComboBoxLabel.visibleProperty().bind(currencyComboBox.visibleProperty());
currencyComboBoxLabel.managedProperty().bind(currencyComboBox.visibleProperty());
currencyTextField.visibleProperty().bind(currencyComboBox.visibleProperty().not());
currencyTextField.managedProperty().bind(currencyComboBox.visibleProperty().not());
currencyTextFieldLabel.visibleProperty().bind(currencyComboBox.visibleProperty().not());
currencyTextFieldLabel.managedProperty().bind(currencyComboBox.visibleProperty().not());
}
private void removeBindings() {
amountBtcLabel.textProperty().unbind();
priceCurrencyLabel.textProperty().unbind();
fixedPriceTextField.disableProperty().unbind();
priceCurrencyLabel.disableProperty().unbind();
marketBasedPriceTextField.disableProperty().unbind();
marketBasedPriceLabel.disableProperty().unbind();
volumeCurrencyLabel.textProperty().unbind();
minAmountBtcLabel.textProperty().unbind();
priceDescriptionLabel.textProperty().unbind();
xLabel.textProperty().unbind();
volumeDescriptionLabel.textProperty().unbind();
amountTextField.textProperty().unbindBidirectional(model.amount);
minAmountTextField.textProperty().unbindBidirectional(model.minAmount);
fixedPriceTextField.textProperty().unbindBidirectional(model.price);
marketBasedPriceTextField.textProperty().unbindBidirectional(model.marketPriceMargin);
marketBasedPriceLabel.prefWidthProperty().unbind();
volumeTextField.textProperty().unbindBidirectional(model.volume);
volumeTextField.promptTextProperty().unbindBidirectional(model.volume);
totalToPayTextField.textProperty().unbind();
addressTextField.amountAsCoinProperty().unbind();
// Validation
amountTextField.validationResultProperty().unbind();
minAmountTextField.validationResultProperty().unbind();
fixedPriceTextField.validationResultProperty().unbind();
volumeTextField.validationResultProperty().unbind();
// funding
fundingHBox.visibleProperty().unbind();
fundingHBox.managedProperty().unbind();
waitingForFundsLabel.textProperty().unbind();
placeOfferButton.visibleProperty().unbind();
placeOfferButton.managedProperty().unbind();
placeOfferButton.disableProperty().unbind();
cancelButton2.disableProperty().unbind();
// trading account
currencyComboBox.managedProperty().unbind();
currencyComboBox.prefWidthProperty().unbind();
currencyComboBoxLabel.visibleProperty().unbind();
currencyComboBoxLabel.managedProperty().unbind();
currencyTextField.visibleProperty().unbind();
currencyTextField.managedProperty().unbind();
currencyTextFieldLabel.visibleProperty().unbind();
currencyTextFieldLabel.managedProperty().unbind();
}
private void addSubscriptions() {
isWaitingForFundsSubscription = EasyBind.subscribe(model.isWaitingForFunds, isWaitingForFunds -> {
waitingForFundsBusyAnimation.setIsRunning(isWaitingForFunds);
waitingForFundsLabel.setVisible(isWaitingForFunds);
waitingForFundsLabel.setManaged(isWaitingForFunds);
});
cancelButton2StyleSubscription = EasyBind.subscribe(placeOfferButton.visibleProperty(),
isVisible -> cancelButton2.setId(isVisible ? "cancel-button" : null));
balanceSubscription = EasyBind.subscribe(model.dataModel.balance, newValue -> balanceTextField.setBalance(newValue));
}
private void removeSubscriptions() {
isWaitingForFundsSubscription.unsubscribe();
cancelButton2StyleSubscription.unsubscribe();
balanceSubscription.unsubscribe();
}
private void createListeners() {
amountFocusedListener = (o, oldValue, newValue) -> {
model.onFocusOutAmountTextField(oldValue, newValue, amountTextField.getText());
amountTextField.setText(model.amount.get());
};
minAmountFocusedListener = (o, oldValue, newValue) -> {
model.onFocusOutMinAmountTextField(oldValue, newValue, minAmountTextField.getText());
minAmountTextField.setText(model.minAmount.get());
};
priceFocusedListener = (o, oldValue, newValue) -> {
model.onFocusOutPriceTextField(oldValue, newValue, fixedPriceTextField.getText());
fixedPriceTextField.setText(model.price.get());
};
priceAsPercentageFocusedListener = (o, oldValue, newValue) -> {
model.onFocusOutPriceAsPercentageTextField(oldValue, newValue, marketBasedPriceTextField.getText());
marketBasedPriceTextField.setText(model.marketPriceMargin.get());
};
volumeFocusedListener = (o, oldValue, newValue) -> {
model.onFocusOutVolumeTextField(oldValue, newValue, volumeTextField.getText());
volumeTextField.setText(model.volume.get());
};
errorMessageListener = (o, oldValue, newValue) -> {
if (newValue != null)
UserThread.runAfter(() -> new Popup().error(BSResources.get("createOffer.amountPriceBox.error.message", model.errorMessage.get()) +
"\n\nNo funds have left your wallet yet.\n" +
"Please try to restart you application and check your network connection to see if you can resolve the issue.")
.show(), 100, TimeUnit.MILLISECONDS);
};
/* feeFromFundingTxListener = (observable, oldValue, newValue) -> {
log.debug("feeFromFundingTxListener " + newValue);
if (!model.dataModel.isFeeFromFundingTxSufficient()) {
new Popup().warning("The mining fee from your funding transaction is not sufficiently high.\n\n" +
"You need to use at least a mining fee of " +
model.formatter.formatCoinWithCode(FeePolicy.getMinRequiredFeeForFundingTx()) + ".\n\n" +
"The fee used in your funding transaction was only " +
model.formatter.formatCoinWithCode(newValue) + ".\n\n" +
"The trade transactions might take too much time to be included in " +
"a block if the fee is too low.\n" +
"Please check at your external wallet that you set the required fee and " +
"do a funding again with the correct fee.\n\n" +
"In the \"Funds/Open for withdrawal\" section you can withdraw those funds.")
.closeButtonText("Close")
.onClose(() -> {
close();
model.dataModel.swapTradeToSavings();
navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class);
})
.show();
}
};*/
paymentAccountsComboBoxSelectionHandler = e -> onPaymentAccountsComboBoxSelected();
currencyComboBoxSelectionHandler = e -> onCurrencyComboBoxSelected();
tradeCurrencyCodeListener = (observable, oldValue, newValue) -> {
fixedPriceTextField.clear();
marketBasedPriceTextField.clear();
volumeTextField.clear();
};
placeOfferCompletedListener = (o, oldValue, newValue) -> {
if (DevFlags.DEV_MODE) {
close();
} else if (newValue) {
// We need a bit of delay to avoid issues with fade out/fade in of 2 popups
String key = "createOfferSuccessInfo";
if (preferences.showAgain(key)) {
UserThread.runAfter(() -> new Popup().headLine(BSResources.get("createOffer.success.headline"))
.feedback(BSResources.get("createOffer.success.info"))
.dontShowAgainId(key, preferences)
.actionButtonText("Go to \"My open offers\"")
.onAction(() -> {
UserThread.runAfter(() ->
navigation.navigateTo(MainView.class, PortfolioView.class, OpenOffersView.class),
100, TimeUnit.MILLISECONDS);
close();
})
.onClose(this::close)
.show(),
1);
} else {
close();
}
}
};
}
private void addListeners() {
model.tradeCurrencyCode.addListener(tradeCurrencyCodeListener);
// focus out
amountTextField.focusedProperty().addListener(amountFocusedListener);
minAmountTextField.focusedProperty().addListener(minAmountFocusedListener);
fixedPriceTextField.focusedProperty().addListener(priceFocusedListener);
marketBasedPriceTextField.focusedProperty().addListener(priceAsPercentageFocusedListener);
volumeTextField.focusedProperty().addListener(volumeFocusedListener);
// warnings
model.errorMessage.addListener(errorMessageListener);
// model.dataModel.feeFromFundingTxProperty.addListener(feeFromFundingTxListener);
model.placeOfferCompleted.addListener(placeOfferCompletedListener);
// UI actions
paymentAccountsComboBox.setOnAction(paymentAccountsComboBoxSelectionHandler);
currencyComboBox.setOnAction(currencyComboBoxSelectionHandler);
}
private void removeListeners() {
model.tradeCurrencyCode.removeListener(tradeCurrencyCodeListener);
// focus out
amountTextField.focusedProperty().removeListener(amountFocusedListener);
minAmountTextField.focusedProperty().removeListener(minAmountFocusedListener);
fixedPriceTextField.focusedProperty().removeListener(priceFocusedListener);
marketBasedPriceTextField.focusedProperty().removeListener(priceAsPercentageFocusedListener);
volumeTextField.focusedProperty().removeListener(volumeFocusedListener);
// warnings
model.errorMessage.removeListener(errorMessageListener);
// model.dataModel.feeFromFundingTxProperty.removeListener(feeFromFundingTxListener);
model.placeOfferCompleted.removeListener(placeOfferCompletedListener);
// UI actions
paymentAccountsComboBox.setOnAction(null);
currencyComboBox.setOnAction(null);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Build UI elements
///////////////////////////////////////////////////////////////////////////////////////////
private void addScrollPane() {
scrollPane = new ScrollPane();
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED);
scrollPane.setFitToWidth(true);
scrollPane.setFitToHeight(true);
scrollPane.setOnScroll(e -> InputTextField.hideErrorMessageDisplay());
AnchorPane.setLeftAnchor(scrollPane, 0d);
AnchorPane.setTopAnchor(scrollPane, 0d);
AnchorPane.setRightAnchor(scrollPane, 0d);
AnchorPane.setBottomAnchor(scrollPane, 0d);
root.getChildren().add(scrollPane);
}
private void addGridPane() {
gridPane = new GridPane();
gridPane.setPadding(new Insets(30, 25, -1, 25));
gridPane.setHgap(5);
gridPane.setVgap(5);
ColumnConstraints columnConstraints1 = new ColumnConstraints();
columnConstraints1.setHalignment(HPos.RIGHT);
columnConstraints1.setHgrow(Priority.NEVER);
columnConstraints1.setMinWidth(200);
ColumnConstraints columnConstraints2 = new ColumnConstraints();
columnConstraints2.setHgrow(Priority.ALWAYS);
ColumnConstraints columnConstraints3 = new ColumnConstraints();
columnConstraints3.setHgrow(Priority.NEVER);
gridPane.getColumnConstraints().addAll(columnConstraints1, columnConstraints2, columnConstraints3);
scrollPane.setContent(gridPane);
}
private void addPaymentGroup() {
TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, gridRow, 2, "Select trading account");
GridPane.setColumnSpan(titledGroupBg, 3);
paymentAccountsComboBox = addLabelComboBox(gridPane, gridRow, "Trading account:", Layout.FIRST_ROW_DISTANCE).second;
paymentAccountsComboBox.setPromptText("Select trading account");
paymentAccountsComboBox.setMinWidth(300);
editOfferElements.add(paymentAccountsComboBox);
// we display either currencyComboBox (multi currency account) or currencyTextField (single)
Tuple2<Label, ComboBox> currencyComboBoxTuple = addLabelComboBox(gridPane, ++gridRow, "Currency:");
currencyComboBoxLabel = currencyComboBoxTuple.first;
editOfferElements.add(currencyComboBoxLabel);
currencyComboBox = currencyComboBoxTuple.second;
editOfferElements.add(currencyComboBox);
currencyComboBox.setPromptText("Select currency");
currencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
@Override
public String toString(TradeCurrency tradeCurrency) {
return tradeCurrency.getNameAndCode();
}
@Override
public TradeCurrency fromString(String s) {
return null;
}
});
Tuple2<Label, TextField> currencyTextFieldTuple = addLabelTextField(gridPane, gridRow, "Currency:", "", 5);
currencyTextFieldLabel = currencyTextFieldTuple.first;
editOfferElements.add(currencyTextFieldLabel);
currencyTextField = currencyTextFieldTuple.second;
editOfferElements.add(currencyTextField);
}
private void addAmountPriceGroup() {
TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, ++gridRow, 2, "Set amount and price", Layout.GROUP_DISTANCE);
GridPane.setColumnSpan(titledGroupBg, 3);
imageView = new ImageView();
imageView.setPickOnBounds(true);
directionLabel = new Label();
directionLabel.setAlignment(Pos.CENTER);
directionLabel.setPadding(new Insets(-5, 0, 0, 0));
directionLabel.setId("direction-icon-label");
VBox imageVBox = new VBox();
imageVBox.setAlignment(Pos.CENTER);
imageVBox.setSpacing(6);
imageVBox.getChildren().addAll(imageView, directionLabel);
GridPane.setRowIndex(imageVBox, gridRow);
GridPane.setRowSpan(imageVBox, 2);
GridPane.setMargin(imageVBox, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, 10, 10, 10));
gridPane.getChildren().add(imageVBox);
addAmountPriceFields();
addSecondRow();
Tuple2<Button, Button> tuple = add2ButtonsAfterGroup(gridPane, ++gridRow, BSResources.get("createOffer.amountPriceBox.next"), BSResources.get("shared.cancel"));
nextButton = tuple.first;
editOfferElements.add(nextButton);
nextButton.disableProperty().bind(model.isNextButtonDisabled);
//UserThread.runAfter(() -> nextButton.requestFocus(), 100, TimeUnit.MILLISECONDS);
cancelButton1 = tuple.second;
editOfferElements.add(cancelButton1);
cancelButton1.setDefaultButton(false);
cancelButton1.setOnAction(e -> {
close();
model.dataModel.swapTradeToSavings();
});
cancelButton1.setId("cancel-button");
GridPane.setMargin(nextButton, new Insets(-35, 0, 0, 0));
nextButton.setOnAction(e -> {
if (model.isPriceInRange())
onShowPayFundsScreen();
});
}
private void addFundingGroup() {
// don't increase gridRow as we removed button when this gets visible
payFundsPane = addTitledGroupBg(gridPane, gridRow, 3, BSResources.get("createOffer.fundsBox.title"), Layout.GROUP_DISTANCE);
GridPane.setColumnSpan(payFundsPane, 3);
payFundsPane.setVisible(false);
totalToPayLabel = new Label(BSResources.get("createOffer.fundsBox.totalsNeeded"));
totalToPayLabel.setVisible(false);
totalToPayInfoIconLabel = new Label();
totalToPayInfoIconLabel.setVisible(false);
HBox totalToPayBox = new HBox();
totalToPayBox.setSpacing(4);
totalToPayBox.setAlignment(Pos.CENTER_RIGHT);
totalToPayBox.getChildren().addAll(totalToPayLabel, totalToPayInfoIconLabel);
GridPane.setMargin(totalToPayBox, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
GridPane.setRowIndex(totalToPayBox, gridRow);
gridPane.getChildren().add(totalToPayBox);
totalToPayTextField = new TextFieldWithCopyIcon();
totalToPayTextField.setCopyWithoutCurrencyPostFix(true);
totalToPayTextField.setFocusTraversable(false);
totalToPayTextField.setVisible(false);
GridPane.setRowIndex(totalToPayTextField, gridRow);
GridPane.setColumnIndex(totalToPayTextField, 1);
GridPane.setMargin(totalToPayTextField, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
gridPane.getChildren().add(totalToPayTextField);
qrCodeImageView = new ImageView();
qrCodeImageView.setVisible(false);
qrCodeImageView.setStyle("-fx-cursor: hand;");
Tooltip.install(qrCodeImageView, new Tooltip("Open large QR-Code window"));
qrCodeImageView.setOnMouseClicked(e -> GUIUtil.showFeeInfoBeforeExecute(
() -> UserThread.runAfter(
() -> new QRCodeWindow(getBitcoinURI()).show(),
200, TimeUnit.MILLISECONDS)
));
GridPane.setRowIndex(qrCodeImageView, gridRow);
GridPane.setColumnIndex(qrCodeImageView, 2);
GridPane.setRowSpan(qrCodeImageView, 3);
GridPane.setMargin(qrCodeImageView, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE - 9, 0, 0, 5));
gridPane.getChildren().add(qrCodeImageView);
Tuple2<Label, AddressTextField> addressTuple = addLabelAddressTextField(gridPane, ++gridRow, BSResources.get("createOffer.fundsBox.address"));
addressLabel = addressTuple.first;
addressLabel.setVisible(false);
addressTextField = addressTuple.second;
addressTextField.setVisible(false);
Tuple2<Label, BalanceTextField> balanceTuple = addLabelBalanceTextField(gridPane, ++gridRow, BSResources.get("createOffer.fundsBox.balance"));
balanceLabel = balanceTuple.first;
balanceLabel.setVisible(false);
balanceTextField = balanceTuple.second;
balanceTextField.setVisible(false);
fundingHBox = new HBox();
fundingHBox.setVisible(false);
fundingHBox.setManaged(false);
fundingHBox.setSpacing(10);
fundFromSavingsWalletButton = new Button("Transfer funds from Bitsquare wallet");
fundFromSavingsWalletButton.setDefaultButton(true);
fundFromSavingsWalletButton.setDefaultButton(false);
fundFromSavingsWalletButton.setOnAction(e -> model.fundFromSavingsWallet());
Label label = new Label("OR");
label.setPadding(new Insets(5, 0, 0, 0));
fundFromExternalWalletButton = new Button("Open your external wallet for funding");
fundFromExternalWalletButton.setDefaultButton(false);
fundFromExternalWalletButton.setOnAction(e -> GUIUtil.showFeeInfoBeforeExecute(this::openWallet));
waitingForFundsBusyAnimation = new BusyAnimation();
waitingForFundsLabel = new Label();
waitingForFundsLabel.setPadding(new Insets(5, 0, 0, 0));
fundingHBox.getChildren().addAll(fundFromSavingsWalletButton, label, fundFromExternalWalletButton, waitingForFundsBusyAnimation, waitingForFundsLabel);
GridPane.setRowIndex(fundingHBox, ++gridRow);
GridPane.setColumnIndex(fundingHBox, 1);
GridPane.setMargin(fundingHBox, new Insets(15, 10, 0, 0));
gridPane.getChildren().add(fundingHBox);
placeOfferButton = addButtonAfterGroup(gridPane, gridRow, "");
placeOfferButton.setOnAction(e -> onPlaceOffer());
placeOfferButton.setMinHeight(40);
placeOfferButton.setPadding(new Insets(0, 20, 0, 20));
cancelButton2 = addButton(gridPane, ++gridRow, BSResources.get("shared.cancel"));
cancelButton2.setOnAction(e -> {
if (model.dataModel.isWalletFunded.get()) {
new Popup().warning("You have already funded that offer.\n" +
"If you cancel now, your funds will be moved to your local Bitsquare wallet and are available " +
"for withdrawal in the \"Funds/Available for withdrawal\" screen.\n" +
"Are you sure you want to cancel?")
.closeButtonText("No")
.actionButtonText("Yes, cancel")
.onAction(() -> {
close();
model.dataModel.swapTradeToSavings();
})
.show();
} else {
close();
model.dataModel.swapTradeToSavings();
}
});
cancelButton2.setDefaultButton(false);
cancelButton2.setVisible(false);
}
private void openWallet() {
try {
Utilities.openURI(URI.create(getBitcoinURI()));
} catch (Exception ex) {
log.warn(ex.getMessage());
new Popup().warning("Opening a default bitcoin wallet application has failed. " +
"Perhaps you don't have one installed?").show();
}
}
@NotNull
private String getBitcoinURI() {
return model.getAddressAsString() != null ? BitcoinURI.convertToBitcoinURI(model.getAddressAsString(), model.dataModel.missingCoin.get(),
model.getPaymentLabel(), null) : "";
}
private void addAmountPriceFields() {
// amountBox
Tuple3<HBox, InputTextField, Label> amountValueCurrencyBoxTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.amount.prompt"));
HBox amountValueCurrencyBox = amountValueCurrencyBoxTuple.first;
amountTextField = amountValueCurrencyBoxTuple.second;
editOfferElements.add(amountTextField);
amountBtcLabel = amountValueCurrencyBoxTuple.third;
editOfferElements.add(amountBtcLabel);
Tuple2<Label, VBox> amountInputBoxTuple = getTradeInputBox(amountValueCurrencyBox, model.getAmountDescription());
amountDescriptionLabel = amountInputBoxTuple.first;
editOfferElements.add(amountDescriptionLabel);
VBox amountBox = amountInputBoxTuple.second;
// x
xLabel = new Label();
xLabel.setFont(Font.font("Helvetica-Bold", 20));
xLabel.setPadding(new Insets(14, 3, 0, 3));
xLabel.setMinWidth(14);
xLabel.setMaxWidth(14);
// price as fiat
Tuple3<HBox, InputTextField, Label> priceValueCurrencyBoxTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.price.prompt"));
HBox priceValueCurrencyBox = priceValueCurrencyBoxTuple.first;
fixedPriceTextField = priceValueCurrencyBoxTuple.second;
editOfferElements.add(fixedPriceTextField);
priceCurrencyLabel = priceValueCurrencyBoxTuple.third;
editOfferElements.add(priceCurrencyLabel);
Tuple2<Label, VBox> priceInputBoxTuple = getTradeInputBox(priceValueCurrencyBox, "");
priceDescriptionLabel = priceInputBoxTuple.first;
editOfferElements.add(priceDescriptionLabel);
VBox priceBox = priceInputBoxTuple.second;
// Fixed/Percentage toggle
ToggleGroup toggleGroup = new ToggleGroup();
fixedPriceButton = new ToggleButton("Fixed");
editOfferElements.add(fixedPriceButton);
fixedPriceButton.setId("toggle-price-left");
fixedPriceButton.setToggleGroup(toggleGroup);
fixedPriceButton.selectedProperty().addListener((ov, oldValue, newValue) -> {
model.dataModel.setUseMarketBasedPrice(!newValue);
useMarketBasedPriceButton.setSelected(!newValue);
});
useMarketBasedPriceButton = new ToggleButton("Percentage");
editOfferElements.add(useMarketBasedPriceButton);
useMarketBasedPriceButton.setId("toggle-price-right");
useMarketBasedPriceButton.setToggleGroup(toggleGroup);
useMarketBasedPriceButton.selectedProperty().addListener((ov, oldValue, newValue) -> {
model.dataModel.setUseMarketBasedPrice(newValue);
fixedPriceButton.setSelected(!newValue);
});
HBox toggleButtons = new HBox();
toggleButtons.setPadding(new Insets(18, 0, 0, 0));
toggleButtons.getChildren().addAll(fixedPriceButton, useMarketBasedPriceButton);
// =
Label resultLabel = new Label("=");
resultLabel.setFont(Font.font("Helvetica-Bold", 20));
resultLabel.setPadding(new Insets(14, 2, 0, 2));
// volume
Tuple3<HBox, InputTextField, Label> volumeValueCurrencyBoxTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.volume.prompt"));
HBox volumeValueCurrencyBox = volumeValueCurrencyBoxTuple.first;
volumeTextField = volumeValueCurrencyBoxTuple.second;
editOfferElements.add(volumeTextField);
volumeCurrencyLabel = volumeValueCurrencyBoxTuple.third;
editOfferElements.add(volumeCurrencyLabel);
Tuple2<Label, VBox> volumeInputBoxTuple = getTradeInputBox(volumeValueCurrencyBox, model.volumeDescriptionLabel.get());
volumeDescriptionLabel = volumeInputBoxTuple.first;
editOfferElements.add(volumeDescriptionLabel);
VBox volumeBox = volumeInputBoxTuple.second;
HBox hBox = new HBox();
hBox.setSpacing(5);
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.getChildren().addAll(amountBox, xLabel, priceBox, toggleButtons, resultLabel, volumeBox);
GridPane.setRowIndex(hBox, gridRow);
GridPane.setColumnIndex(hBox, 1);
GridPane.setMargin(hBox, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, 10, 0, 0));
GridPane.setColumnSpan(hBox, 2);
gridPane.getChildren().add(hBox);
}
private void addSecondRow() {
Tuple3<HBox, InputTextField, Label> priceAsPercentageTuple = FormBuilder.getValueCurrencyBox(BSResources.get("createOffer.price.prompt"));
HBox priceAsPercentageValueCurrencyBox = priceAsPercentageTuple.first;
marketBasedPriceTextField = priceAsPercentageTuple.second;
editOfferElements.add(marketBasedPriceTextField);
marketBasedPriceLabel = priceAsPercentageTuple.third;
editOfferElements.add(marketBasedPriceLabel);
Tuple2<Label, VBox> priceAsPercentageInputBoxTuple = getTradeInputBox(priceAsPercentageValueCurrencyBox, "Distance in % from market price");
priceAsPercentageInputBoxTuple.first.setPrefWidth(200);
VBox priceAsPercentageInputBox = priceAsPercentageInputBoxTuple.second;
marketBasedPriceTextField.setPromptText("Enter % value");
marketBasedPriceLabel.setText("%");
marketBasedPriceLabel.setStyle("-fx-alignment: center;");
Tuple3<HBox, InputTextField, Label> amountValueCurrencyBoxTuple = getValueCurrencyBox(BSResources.get("createOffer.amount.prompt"));
HBox amountValueCurrencyBox = amountValueCurrencyBoxTuple.first;
minAmountTextField = amountValueCurrencyBoxTuple.second;
editOfferElements.add(minAmountTextField);
minAmountBtcLabel = amountValueCurrencyBoxTuple.third;
editOfferElements.add(minAmountBtcLabel);
Tuple2<Label, VBox> amountInputBoxTuple = getTradeInputBox(amountValueCurrencyBox, BSResources.get("createOffer.amountPriceBox" +
".minAmountDescription"));
Label xLabel = new Label("x");
xLabel.setFont(Font.font("Helvetica-Bold", 20));
xLabel.setPadding(new Insets(14, 3, 0, 3));
xLabel.setVisible(false); // we just use it to get the same layout as the upper row
HBox hBox = new HBox();
hBox.setSpacing(5);
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.getChildren().addAll(amountInputBoxTuple.second, xLabel, priceAsPercentageInputBox);
GridPane.setRowIndex(hBox, ++gridRow);
GridPane.setColumnIndex(hBox, 1);
GridPane.setMargin(hBox, new Insets(5, 10, 5, 0));
GridPane.setColumnSpan(hBox, 2);
gridPane.getChildren().add(hBox);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PayInfo
///////////////////////////////////////////////////////////////////////////////////////////
private void setupTotalToPayInfoIconLabel() {
totalToPayInfoIconLabel.setId("clickable-icon");
AwesomeDude.setIcon(totalToPayInfoIconLabel, AwesomeIcon.QUESTION_SIGN);
totalToPayInfoIconLabel.setOnMouseEntered(e -> createInfoPopover());
totalToPayInfoIconLabel.setOnMouseExited(e -> {
if (totalToPayInfoPopover != null)
totalToPayInfoPopover.hide();
});
}
// As we don't use binding here we need to recreate it on mouse over to reflect the current state
private void createInfoPopover() {
GridPane infoGridPane = new GridPane();
infoGridPane.setHgap(5);
infoGridPane.setVgap(5);
infoGridPane.setPadding(new Insets(10, 10, 10, 10));
int i = 0;
if (model.isSellOffer())
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.tradeAmount"), model.tradeAmount.get());
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.securityDeposit"), model.getSecurityDeposit());
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.offerFee"), model.getOfferFee());
addPayInfoEntry(infoGridPane, i++, BSResources.get("createOffer.fundsBox.networkFee"), model.getNetworkFee());
Separator separator = new Separator();
separator.setOrientation(Orientation.HORIZONTAL);
separator.setStyle("-fx-background: #666;");
GridPane.setConstraints(separator, 1, i++);
infoGridPane.getChildren().add(separator);
addPayInfoEntry(infoGridPane, i, BSResources.get("createOffer.fundsBox.total"), model.totalToPay.get());
totalToPayInfoPopover = new PopOver(infoGridPane);
if (totalToPayInfoIconLabel.getScene() != null) {
totalToPayInfoPopover.setDetachable(false);
totalToPayInfoPopover.setArrowIndent(5);
totalToPayInfoPopover.show(totalToPayInfoIconLabel.getScene().getWindow(),
getPopupPosition().getX(),
getPopupPosition().getY());
}
}
private void addPayInfoEntry(GridPane infoGridPane, int row, String labelText, String value) {
Label label = new Label(labelText);
TextField textField = new TextField(value);
textField.setEditable(false);
textField.setFocusTraversable(false);
textField.setId("payment-info");
GridPane.setConstraints(label, 0, row, 1, 1, HPos.RIGHT, VPos.CENTER);
GridPane.setConstraints(textField, 1, row);
infoGridPane.getChildren().addAll(label, textField);
}
private Point2D getPopupPosition() {
Window window = totalToPayInfoIconLabel.getScene().getWindow();
Point2D point = totalToPayInfoIconLabel.localToScene(0, 0);
double x = point.getX() + window.getX() + totalToPayInfoIconLabel.getWidth() + 2;
double y = point.getY() + window.getY() + Math.floor(totalToPayInfoIconLabel.getHeight() / 2) - 9;
return new Point2D(x, y);
}
}