/*
* 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.offerbook;
import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.components.HyperlinkWithIcon;
import io.bitsquare.gui.components.PeerInfoIcon;
import io.bitsquare.gui.components.TitledGroupBg;
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.content.fiataccounts.FiatAccountsView;
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.util.BSFormatter;
import io.bitsquare.gui.util.GUIUtil;
import io.bitsquare.gui.util.Layout;
import io.bitsquare.locale.BSResources;
import io.bitsquare.locale.FiatCurrency;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.payment.PaymentMethod;
import io.bitsquare.trade.offer.Offer;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.util.Callback;
import javafx.util.StringConverter;
import org.bitcoinj.utils.Fiat;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.fxmisc.easybind.monadic.MonadicBinding;
import javax.inject.Inject;
import static io.bitsquare.gui.util.FormBuilder.*;
@FxmlView
public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookViewModel> {
private final Navigation navigation;
private final OfferDetailsWindow offerDetailsWindow;
private BSFormatter formatter;
private PrivateNotificationManager privateNotificationManager;
private ComboBox<TradeCurrency> currencyComboBox;
private ComboBox<PaymentMethod> paymentMethodComboBox;
private Button createOfferButton;
private TableColumn<OfferBookListItem, OfferBookListItem> amountColumn, volumeColumn, marketColumn, priceColumn, paymentMethodColumn, avatarColumn;
private TableView<OfferBookListItem> tableView;
private OfferView.OfferActionHandler offerActionHandler;
private int gridRow = 0;
private TitledGroupBg offerBookTitle;
private Label nrOfOffersLabel;
private ListChangeListener<OfferBookListItem> offerListListener;
private MonadicBinding<Void> currencySelectionBinding;
private Subscription currencySelectionSubscriber;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
OfferBookView(OfferBookViewModel model, Navigation navigation, OfferDetailsWindow offerDetailsWindow, BSFormatter formatter, PrivateNotificationManager privateNotificationManager) {
super(model);
this.navigation = navigation;
this.offerDetailsWindow = offerDetailsWindow;
this.formatter = formatter;
this.privateNotificationManager = privateNotificationManager;
}
@Override
public void initialize() {
root.setPadding(new Insets(20, 25, 5, 25));
offerBookTitle = addTitledGroupBg(root, gridRow, 3, "Available offers");
currencyComboBox = addLabelComboBox(root, gridRow, "Filter by currency:", Layout.FIRST_ROW_DISTANCE).second;
currencyComboBox.setPromptText("Select currency");
currencyComboBox.setConverter(GUIUtil.getTradeCurrencyConverter());
paymentMethodComboBox = addLabelComboBox(root, ++gridRow, "Filter by payment method:").second;
paymentMethodComboBox.setPromptText("Select payment method");
paymentMethodComboBox.setVisibleRowCount(20);
paymentMethodComboBox.setConverter(new StringConverter<PaymentMethod>() {
@Override
public String toString(PaymentMethod paymentMethod) {
String id = paymentMethod.getId();
if (id.equals(GUIUtil.SHOW_ALL_FLAG))
return "▶ Show all";
else if (paymentMethod.equals(PaymentMethod.BLOCK_CHAINS))
return "✦ " + BSResources.get(id);
else
return "★ " + BSResources.get(id);
}
@Override
public PaymentMethod fromString(String s) {
return null;
}
});
tableView = new TableView<>();
GridPane.setRowIndex(tableView, ++gridRow);
GridPane.setColumnIndex(tableView, 0);
GridPane.setColumnSpan(tableView, 2);
GridPane.setMargin(tableView, new Insets(10, -10, -10, -10));
GridPane.setVgrow(tableView, Priority.ALWAYS);
root.getChildren().add(tableView);
marketColumn = getMarketColumn();
priceColumn = getPriceColumn();
tableView.getColumns().add(priceColumn);
amountColumn = getAmountColumn();
tableView.getColumns().add(amountColumn);
volumeColumn = getVolumeColumn();
tableView.getColumns().add(volumeColumn);
paymentMethodColumn = getPaymentMethodColumn();
tableView.getColumns().add(paymentMethodColumn);
tableView.getColumns().add(getActionColumn());
avatarColumn = getAvatarColumn();
tableView.getColumns().add(avatarColumn);
tableView.getSortOrder().add(priceColumn);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
Label placeholder = new Label("Currently there are no offers available");
placeholder.setWrapText(true);
tableView.setPlaceholder(placeholder);
priceColumn.setComparator((o1, o2) -> {
Fiat price1 = o1.getOffer().getPrice();
Fiat price2 = o2.getOffer().getPrice();
return price1 != null && price2 != null ? price1.compareTo(price2) : 0;
});
amountColumn.setComparator((o1, o2) -> o1.getOffer().getAmount().compareTo(o2.getOffer().getAmount()));
volumeColumn.setComparator((o1, o2) -> {
Fiat offerVolume1 = o1.getOffer().getOfferVolume();
Fiat offerVolume2 = o2.getOffer().getOfferVolume();
return offerVolume1 != null && offerVolume2 != null ? offerVolume1.compareTo(offerVolume2) : 0;
});
paymentMethodColumn.setComparator((o1, o2) -> o1.getOffer().getPaymentMethod().compareTo(o2.getOffer().getPaymentMethod()));
avatarColumn.setComparator((o1, o2) -> o1.getOffer().getOwnerNodeAddress().hostName.compareTo(o2.getOffer().getOwnerNodeAddress().hostName));
nrOfOffersLabel = new Label("");
nrOfOffersLabel.setId("num-offers");
GridPane.setHalignment(nrOfOffersLabel, HPos.LEFT);
GridPane.setVgrow(nrOfOffersLabel, Priority.NEVER);
GridPane.setValignment(nrOfOffersLabel, VPos.TOP);
GridPane.setRowIndex(nrOfOffersLabel, ++gridRow);
GridPane.setColumnIndex(nrOfOffersLabel, 0);
GridPane.setMargin(nrOfOffersLabel, new Insets(10, 0, 0, -5));
root.getChildren().add(nrOfOffersLabel);
createOfferButton = addButton(root, gridRow, "");
createOfferButton.setMinHeight(40);
createOfferButton.setPadding(new Insets(0, 20, 0, 20));
createOfferButton.setGraphicTextGap(10);
GridPane.setMargin(createOfferButton, new Insets(15, 0, 0, 0));
GridPane.setHalignment(createOfferButton, HPos.RIGHT);
GridPane.setVgrow(createOfferButton, Priority.NEVER);
GridPane.setValignment(createOfferButton, VPos.TOP);
offerListListener = c -> nrOfOffersLabel.setText("No. of offers: " + model.getOfferList().size());
}
@Override
protected void activate() {
currencyComboBox.setItems(model.getTradeCurrencies());
currencyComboBox.setVisibleRowCount(Math.min(currencyComboBox.getItems().size(), 25));
currencyComboBox.setOnAction(e -> model.onSetTradeCurrency(currencyComboBox.getSelectionModel().getSelectedItem()));
if (model.showAllTradeCurrenciesProperty.get())
currencyComboBox.getSelectionModel().select(0);
else
currencyComboBox.getSelectionModel().select(model.getSelectedTradeCurrency());
priceColumn.sortableProperty().bind(model.showAllTradeCurrenciesProperty.not());
volumeColumn.sortableProperty().bind(model.showAllTradeCurrenciesProperty.not());
paymentMethodComboBox.setItems(model.getPaymentMethods());
paymentMethodComboBox.setOnAction(e -> model.onSetPaymentMethod(paymentMethodComboBox.getSelectionModel().getSelectedItem()));
if (model.showAllPaymentMethods)
paymentMethodComboBox.getSelectionModel().select(0);
else
paymentMethodComboBox.getSelectionModel().select(model.selectedPaymentMethod);
createOfferButton.setOnAction(e -> onCreateOffer());
currencySelectionBinding = EasyBind.combine(
model.showAllTradeCurrenciesProperty, model.tradeCurrencyCode,
(showAll, code) -> {
setDirectionTitles();
if (showAll) {
volumeColumn.setText("Amount (min - max)");
priceColumn.setText("Price");
if (!tableView.getColumns().contains(marketColumn))
tableView.getColumns().add(0, marketColumn);
} else {
volumeColumn.setText(code + " (min - max)");
priceColumn.setText(formatter.getPriceWithCurrencyCode(code));
if (tableView.getColumns().contains(marketColumn))
tableView.getColumns().remove(marketColumn);
}
return null;
});
currencySelectionSubscriber = currencySelectionBinding.subscribe((observable, oldValue, newValue) -> {
});
model.getOfferList().comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(model.getOfferList());
priceColumn.setSortType((model.getDirection() == Offer.Direction.BUY) ? TableColumn.SortType.ASCENDING : TableColumn.SortType.DESCENDING);
model.getOfferList().addListener(offerListListener);
nrOfOffersLabel.setText("No. of offers: " + model.getOfferList().size());
}
@Override
protected void deactivate() {
currencyComboBox.setOnAction(null);
paymentMethodComboBox.setOnAction(null);
createOfferButton.setOnAction(null);
model.getOfferList().comparatorProperty().unbind();
priceColumn.sortableProperty().unbind();
amountColumn.sortableProperty().unbind();
model.getOfferList().removeListener(offerListListener);
currencySelectionSubscriber.unsubscribe();
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
public void enableCreateOfferButton() {
createOfferButton.setDisable(false);
}
public void setDirection(Offer.Direction direction) {
model.initWithDirection(direction);
ImageView iconView = new ImageView();
createOfferButton.setGraphic(iconView);
iconView.setId(direction == Offer.Direction.SELL ? "image-sell-white" : "image-buy-white");
createOfferButton.setId(direction == Offer.Direction.SELL ? "sell-button-big" : "buy-button-big");
setDirectionTitles();
}
private void setDirectionTitles() {
TradeCurrency selectedTradeCurrency = model.getSelectedTradeCurrency();
if (selectedTradeCurrency != null) {
Offer.Direction direction = model.getDirection();
String preFix = "Create new offer to ";
String directionText = direction == Offer.Direction.BUY ? "buy" : "sell";
String mirroredDirectionText = direction == Offer.Direction.SELL ? "buy" : "sell";
String code = selectedTradeCurrency.getCode();
if (model.showAllTradeCurrenciesProperty.get())
createOfferButton.setText(preFix + directionText + " BTC");
else if (selectedTradeCurrency instanceof FiatCurrency)
createOfferButton.setText(preFix + directionText + " BTC" + (direction == Offer.Direction.BUY ? " with " : " for ") + code);
else
createOfferButton.setText(preFix + mirroredDirectionText + " " + code + " (" + directionText + " BTC)");
}
}
public void setOfferActionHandler(OfferView.OfferActionHandler offerActionHandler) {
this.offerActionHandler = offerActionHandler;
}
public void onTabSelected(boolean isSelected) {
model.onTabSelected(isSelected);
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions
///////////////////////////////////////////////////////////////////////////////////////////
private void onCreateOffer() {
if (!model.hasPaymentAccount()) {
openPopupForMissingAccountSetup("You have not setup a trading account",
"You need to setup a national currency or altcoin account before you can create an offer.\n" +
"Do you want to setup an account?", FiatAccountsView.class, "\"Account\"");
} else if (!model.hasPaymentAccountForCurrency()) {
new Popup().headLine("No trading account for selected currency")
.instruction("You don't have a trading account for the selected currency.\n" +
"Do you want to create an offer with one of your existing trading accounts?")
.actionButtonText("Yes, create offer")
.onAction(() -> {
createOfferButton.setDisable(true);
offerActionHandler.onCreateOffer(model.getSelectedTradeCurrency());
})
.closeButtonText("Set up a new trading account")
.onClose(() -> {
navigation.setReturnPath(navigation.getCurrentPath());
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class, FiatAccountsView.class);
})
.show();
} else if (!model.hasAcceptedArbitrators()) {
openPopupForMissingAccountSetup("You don't have an arbitrator selected.",
"You need to setup at least one arbitrator to be able to trade.\n" +
"Do you want to do this now?", ArbitratorSelectionView.class, "\"Arbitrator selection\"");
} else {
createOfferButton.setDisable(true);
offerActionHandler.onCreateOffer(model.getSelectedTradeCurrency());
}
}
private void onShowInfo(boolean isPaymentAccountValidForOffer, boolean hasMatchingArbitrator,
boolean hasSameProtocolVersion, boolean isIgnored,
boolean isOfferBanned, boolean isNodeBanned) {
if (!hasMatchingArbitrator) {
openPopupForMissingAccountSetup("You don't have an arbitrator selected.",
"You need to setup at least one arbitrator to be able to trade.\n" +
"Do you want to do this now?", ArbitratorSelectionView.class, "\"Arbitrator selection\"");
} else if (!isPaymentAccountValidForOffer) {
openPopupForMissingAccountSetup("No matching trading account.",
"You don't have a trading account with the payment method required for that offer.\n" +
"You need to setup a trading account with that payment method if you want to take this offer.\n" +
"Do you want to do this now?", FiatAccountsView.class, "\"Account\"");
} else if (!hasSameProtocolVersion) {
new Popup().warning("That offer requires a different protocol version as the one used in your " +
"version of the software.\n\n" +
"Please check if you have the latest version installed, otherwise the user " +
"who created the offer has used an older version.\n\n" +
"Users cannot trade with an incompatible trade protocol version.")
.show();
} else if (isIgnored) {
new Popup().warning("You have added that user's onion address to your ignore list.")
.show();
} else if (isOfferBanned) {
new Popup().warning("That offer was blocked by the Bitsquare developers.\n" +
"Probably there is an unhandled bug causing issues when taking that offer.")
.show();
} else if (isNodeBanned) {
new Popup().warning("The onion address of that trader was blocked by the Bitsquare developers.\n" +
"Probably there is an unhandled bug causing issues when taking offers from that trader.")
.show();
}
}
private void onTakeOffer(Offer offer) {
if (model.isBootstrapped())
offerActionHandler.onTakeOffer(offer);
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 onRemoveOpenOffer(Offer offer) {
if (model.isBootstrapped()) {
String key = "RemoveOfferWarning";
if (model.preferences.showAgain(key))
new Popup().warning("Are you sure you want to remove that offer?\n" +
"The offer fee of " + model.formatter.formatCoinWithCode(FeePolicy.getCreateOfferFee()) +
" will be lost if you remove that offer.")
.actionButtonText("Remove offer")
.onAction(() -> doRemoveOffer(offer))
.closeButtonText("Don't remove offer")
.dontShowAgainId(key, model.preferences)
.show();
else
doRemoveOffer(offer);
} 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 doRemoveOffer(Offer offer) {
String key = "WithdrawFundsAfterRemoveOfferInfo";
model.onRemoveOpenOffer(offer,
() -> {
log.debug("Remove offer was successful.");
if (model.preferences.showAgain(key))
new Popup().instruction("You can withdraw the funds you paid in from the \"Fund/Available for withdrawal\" screen.")
.actionButtonText("Go to \"Funds/Available for withdrawal\"")
.onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class))
.dontShowAgainId(key, model.preferences)
.show();
},
(message) -> {
log.error(message);
new Popup().warning("Remove offer failed:\n" + message).show();
});
}
private void openPopupForMissingAccountSetup(String headLine, String message, Class target, String targetAsString) {
new Popup().headLine(headLine)
.instruction(message)
.actionButtonText("Go to " + targetAsString)
.onAction(() -> {
navigation.setReturnPath(navigation.getCurrentPath());
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class, target);
}).show();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Table
///////////////////////////////////////////////////////////////////////////////////////////
private TableColumn<OfferBookListItem, OfferBookListItem> getAmountColumn() {
TableColumn<OfferBookListItem, OfferBookListItem> column = new TableColumn<OfferBookListItem, OfferBookListItem>("BTC (min - max)") {
{
setMinWidth(150);
}
};
column.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
column.setCellFactory(
new Callback<TableColumn<OfferBookListItem, OfferBookListItem>, TableCell<OfferBookListItem,
OfferBookListItem>>() {
@Override
public TableCell<OfferBookListItem, OfferBookListItem> call(
TableColumn<OfferBookListItem, OfferBookListItem> column) {
return new TableCell<OfferBookListItem, OfferBookListItem>() {
@Override
public void updateItem(final OfferBookListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty)
setText(model.getAmount(item));
else
setText("");
}
};
}
});
return column;
}
private TableColumn<OfferBookListItem, OfferBookListItem> getMarketColumn() {
TableColumn<OfferBookListItem, OfferBookListItem> column = new TableColumn<OfferBookListItem, OfferBookListItem>("Market") {
{
setMinWidth(120);
// setMaxWidth(130);
}
};
column.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
column.setCellFactory(
new Callback<TableColumn<OfferBookListItem, OfferBookListItem>, TableCell<OfferBookListItem,
OfferBookListItem>>() {
@Override
public TableCell<OfferBookListItem, OfferBookListItem> call(
TableColumn<OfferBookListItem, OfferBookListItem> column) {
return new TableCell<OfferBookListItem, OfferBookListItem>() {
@Override
public void updateItem(final OfferBookListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty)
setText(formatter.getCurrencyPair(item.getOffer().getCurrencyCode()));
else
setText("");
}
};
}
});
return column;
}
private TableColumn<OfferBookListItem, OfferBookListItem> getPriceColumn() {
TableColumn<OfferBookListItem, OfferBookListItem> column = new TableColumn<OfferBookListItem, OfferBookListItem>() {
{
setMinWidth(120);
}
};
column.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
column.setCellFactory(
new Callback<TableColumn<OfferBookListItem, OfferBookListItem>, TableCell<OfferBookListItem,
OfferBookListItem>>() {
@Override
public TableCell<OfferBookListItem, OfferBookListItem> call(
TableColumn<OfferBookListItem, OfferBookListItem> column) {
return new TableCell<OfferBookListItem, OfferBookListItem>() {
private OfferBookListItem offerBookListItem;
ChangeListener<Number> listener = new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if (offerBookListItem != null && offerBookListItem.getOffer().getPrice() != null) {
setText(model.getPrice(offerBookListItem));
model.priceFeedService.currenciesUpdateFlagProperty().removeListener(listener);
}
}
};
@Override
public void updateItem(final OfferBookListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (item.getOffer().getPrice() == null) {
this.offerBookListItem = item;
model.priceFeedService.currenciesUpdateFlagProperty().addListener(listener);
setText("N/A");
} else {
setText(model.getPrice(item));
}
} else {
if (listener != null)
model.priceFeedService.currenciesUpdateFlagProperty().removeListener(listener);
this.offerBookListItem = null;
setText("");
}
}
};
}
});
return column;
}
private TableColumn<OfferBookListItem, OfferBookListItem> getVolumeColumn() {
TableColumn<OfferBookListItem, OfferBookListItem> column = new TableColumn<OfferBookListItem, OfferBookListItem>() {
{
setMinWidth(125);
}
};
column.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
column.setCellFactory(
new Callback<TableColumn<OfferBookListItem, OfferBookListItem>, TableCell<OfferBookListItem,
OfferBookListItem>>() {
@Override
public TableCell<OfferBookListItem, OfferBookListItem> call(
TableColumn<OfferBookListItem, OfferBookListItem> column) {
return new TableCell<OfferBookListItem, OfferBookListItem>() {
private OfferBookListItem offerBookListItem;
ChangeListener<Number> listener = new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if (offerBookListItem != null && offerBookListItem.getOffer().getOfferVolume() != null) {
setText(model.getVolume(offerBookListItem));
model.priceFeedService.currenciesUpdateFlagProperty().removeListener(listener);
}
}
};
@Override
public void updateItem(final OfferBookListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
if (item.getOffer().getPrice() == null) {
this.offerBookListItem = item;
model.priceFeedService.currenciesUpdateFlagProperty().addListener(listener);
setText("N/A");
} else {
setText(model.getVolume(item));
}
} else {
if (listener != null)
model.priceFeedService.currenciesUpdateFlagProperty().removeListener(listener);
this.offerBookListItem = null;
setText("");
}
}
};
}
});
return column;
}
private TableColumn<OfferBookListItem, OfferBookListItem> getPaymentMethodColumn() {
TableColumn<OfferBookListItem, OfferBookListItem> column = new TableColumn<OfferBookListItem, OfferBookListItem>("Payment method") {
{
setMinWidth(125);
}
};
column.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
column.setCellFactory(
new Callback<TableColumn<OfferBookListItem, OfferBookListItem>, TableCell<OfferBookListItem, OfferBookListItem>>() {
@Override
public TableCell<OfferBookListItem, OfferBookListItem> call(TableColumn<OfferBookListItem, OfferBookListItem> column) {
return new TableCell<OfferBookListItem, OfferBookListItem>() {
private HyperlinkWithIcon field;
@Override
public void updateItem(final OfferBookListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
field = new HyperlinkWithIcon(model.getPaymentMethod(item), true);
field.setOnAction(event -> offerDetailsWindow.show(item.getOffer()));
field.setTooltip(new Tooltip(model.getPaymentMethodToolTip(item)));
setGraphic(field);
} else {
setGraphic(null);
if (field != null)
field.setOnAction(null);
}
}
};
}
});
return column;
}
private TableColumn<OfferBookListItem, OfferBookListItem> getActionColumn() {
TableColumn<OfferBookListItem, OfferBookListItem> column = new TableColumn<OfferBookListItem, OfferBookListItem>("I want to:") {
{
setMinWidth(80);
setSortable(false);
}
};
column.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
column.setCellFactory(
new Callback<TableColumn<OfferBookListItem, OfferBookListItem>, TableCell<OfferBookListItem,
OfferBookListItem>>() {
@Override
public TableCell<OfferBookListItem, OfferBookListItem> call(TableColumn<OfferBookListItem, OfferBookListItem> column) {
return new TableCell<OfferBookListItem, OfferBookListItem>() {
final ImageView iconView = new ImageView();
final Button button = new Button();
boolean isTradable, isPaymentAccountValidForOffer, hasMatchingArbitrator,
hasSameProtocolVersion, isIgnored, isOfferBanned, isNodeBanned;
{
button.setGraphic(iconView);
button.setMinWidth(130);
button.setMaxWidth(130);
button.setGraphicTextGap(10);
}
@Override
public void updateItem(final OfferBookListItem newItem, boolean empty) {
super.updateItem(newItem, empty);
TableRow tableRow = getTableRow();
if (newItem != null && !empty) {
final Offer offer = newItem.getOffer();
boolean myOffer = model.isMyOffer(offer);
if (tableRow != null) {
isPaymentAccountValidForOffer = model.isAnyPaymentAccountValidForOffer(offer);
hasMatchingArbitrator = model.hasMatchingArbitrator(offer);
hasSameProtocolVersion = model.hasSameProtocolVersion(offer);
isIgnored = model.isIgnored(offer);
isOfferBanned = model.isOfferBanned(offer);
isNodeBanned = model.isNodeBanned(offer);
isTradable = isPaymentAccountValidForOffer && hasMatchingArbitrator &&
hasSameProtocolVersion &&
!isIgnored &&
!isOfferBanned &&
!isNodeBanned;
tableRow.setOpacity(isTradable || myOffer ? 1 : 0.4);
if (isTradable) {
// set first row button as default
button.setDefaultButton(getIndex() == 0);
tableRow.setOnMousePressed(null);
} else {
button.setDefaultButton(false);
tableRow.setOnMousePressed(e -> {
// ugly hack to get the icon clickable when deactivated
if (!(e.getTarget() instanceof ImageView || e.getTarget() instanceof Canvas))
onShowInfo(isPaymentAccountValidForOffer, hasMatchingArbitrator,
hasSameProtocolVersion, isIgnored, isOfferBanned, isNodeBanned);
});
//TODO
//tableRow.setTooltip(new Tooltip(""));
}
}
String title;
if (myOffer) {
iconView.setId("image-remove");
title = "Remove";
button.setId("cancel-button");
button.setStyle("-fx-text-fill: #444;"); // does not take the font colors sometimes from the style
button.setOnAction(e -> onRemoveOpenOffer(offer));
} else {
boolean isSellOffer = offer.getDirection() == Offer.Direction.SELL;
iconView.setId(isSellOffer ? "image-buy-white" : "image-sell-white");
button.setId(isSellOffer ? "buy-button" : "sell-button");
button.setStyle("-fx-text-fill: white;"); // does not take the font colors sometimes from the style
title = model.getDirectionLabel(offer);
button.setTooltip(new Tooltip("Take offer for " + model.getDirectionLabelTooltip(offer)));
button.setOnAction(e -> onTakeOffer(offer));
}
if (!myOffer && !isTradable)
button.setOnAction(e -> onShowInfo(isPaymentAccountValidForOffer,
hasMatchingArbitrator, hasSameProtocolVersion,
isIgnored, isOfferBanned, isNodeBanned));
button.setText(title);
setGraphic(button);
} else {
setGraphic(null);
if (button != null)
button.setOnAction(null);
if (tableRow != null) {
tableRow.setOpacity(1);
tableRow.setOnMousePressed(null);
}
}
}
};
}
});
return column;
}
private TableColumn<OfferBookListItem, OfferBookListItem> getAvatarColumn() {
TableColumn<OfferBookListItem, OfferBookListItem> column = new TableColumn<OfferBookListItem, OfferBookListItem>("") {
{
setMinWidth(40);
setMaxWidth(40);
setSortable(true);
}
};
column.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
column.setCellFactory(
new Callback<TableColumn<OfferBookListItem, OfferBookListItem>, TableCell<OfferBookListItem,
OfferBookListItem>>() {
@Override
public TableCell<OfferBookListItem, OfferBookListItem> call(TableColumn<OfferBookListItem, OfferBookListItem> column) {
return new TableCell<OfferBookListItem, OfferBookListItem>() {
@Override
public void updateItem(final OfferBookListItem newItem, boolean empty) {
super.updateItem(newItem, empty);
if (newItem != null && !empty) {
String hostName = newItem.getOffer().getOwnerNodeAddress().hostName;
int numPastTrades = model.getNumPastTrades(newItem.getOffer());
boolean hasTraded = numPastTrades > 0;
String tooltipText = hasTraded ? "Offerer's onion address: " + hostName + "\n" +
"You have already traded " + numPastTrades + " times with that offerer." : "Offerer's onion address: " + hostName;
Node identIcon = new PeerInfoIcon(hostName, tooltipText, numPastTrades, privateNotificationManager, newItem.getOffer());
setPadding(new Insets(-2, 0, -2, 0));
if (identIcon != null)
setGraphic(identIcon);
} else {
setGraphic(null);
}
}
};
}
});
return column;
}
}