/*
* 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.portfolio.pendingtrades;
import io.bitsquare.alert.PrivateNotificationManager;
import io.bitsquare.common.UserThread;
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.main.overlays.popups.Popup;
import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow;
import io.bitsquare.gui.util.BSFormatter;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.transformation.SortedList;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import javax.inject.Inject;
@FxmlView
public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTradesViewModel> {
private final TradeDetailsWindow tradeDetailsWindow;
private final BSFormatter formatter;
private PrivateNotificationManager privateNotificationManager;
@FXML
TableView<PendingTradesListItem> tableView;
@FXML
TableColumn<PendingTradesListItem, PendingTradesListItem> priceColumn, tradeVolumeColumn, tradeAmountColumn, avatarColumn, marketColumn, roleColumn, paymentMethodColumn, idColumn, dateColumn;
@FXML
private SortedList<PendingTradesListItem> sortedList;
private TradeSubView selectedSubView;
private EventHandler<KeyEvent> keyEventEventHandler;
private Scene scene;
private Subscription selectedTableItemSubscription;
private Subscription selectedItemSubscription;
private Subscription appFocusSubscription;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, Initialisation
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public PendingTradesView(PendingTradesViewModel model, TradeDetailsWindow tradeDetailsWindow, BSFormatter formatter, PrivateNotificationManager privateNotificationManager) {
super(model);
this.tradeDetailsWindow = tradeDetailsWindow;
this.formatter = formatter;
this.privateNotificationManager = privateNotificationManager;
}
@Override
public void initialize() {
setTradeIdColumnCellFactory();
setDateColumnCellFactory();
setAmountColumnCellFactory();
setPriceColumnCellFactory();
setVolumeColumnCellFactory();
setPaymentMethodColumnCellFactory();
setMarketColumnCellFactory();
setRoleColumnCellFactory();
setAvatarColumnCellFactory();
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView.setPlaceholder(new Label("No pending trades available"));
tableView.setMinHeight(100);
idColumn.setComparator((o1, o2) -> o1.getTrade().getId().compareTo(o2.getTrade().getId()));
dateColumn.setComparator((o1, o2) -> o1.getTrade().getDate().compareTo(o2.getTrade().getDate()));
tradeVolumeColumn.setComparator((o1, o2) -> {
if (o1.getTrade().getTradeVolume() != null && o2.getTrade().getTradeVolume() != null)
return o1.getTrade().getTradeVolume().compareTo(o2.getTrade().getTradeVolume());
else
return 0;
});
tradeAmountColumn.setComparator((o1, o2) -> {
if (o1.getTrade().getTradeAmount() != null && o2.getTrade().getTradeAmount() != null)
return o1.getTrade().getTradeAmount().compareTo(o2.getTrade().getTradeAmount());
else
return 0;
});
priceColumn.setComparator((o1, o2) -> o1.getPrice().compareTo(o2.getPrice()));
paymentMethodColumn.setComparator((o1, o2) -> o1.getTrade().getOffer().getPaymentMethod().getId().compareTo(o2.getTrade().getOffer().getPaymentMethod().getId()));
avatarColumn.setComparator((o1, o2) -> {
if (o1.getTrade().getTradingPeerNodeAddress() != null && o2.getTrade().getTradingPeerNodeAddress() != null)
return o1.getTrade().getTradingPeerNodeAddress().hostName.compareTo(o2.getTrade().getTradingPeerNodeAddress().hostName);
else
return 0;
});
roleColumn.setComparator((o1, o2) -> model.getMyRole(o1).compareTo(model.getMyRole(o2)));
marketColumn.setComparator((o1, o2) -> model.getMarketLabel(o1).compareTo(model.getMarketLabel(o2)));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
// we use a hidden emergency shortcut to open support ticket
keyEventEventHandler = event -> {
if (new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN).match(event) || new KeyCodeCombination(KeyCode.O, KeyCombination.CONTROL_DOWN).match(event)) {
Popup popup = new Popup();
popup.headLine("Open support ticket")
.message("Please use that only in emergency case if you don't get displayed a \"Open support\" or \"Open dispute\" button.\n\n" +
"When you open a support ticket the trade will be interrupted and handled by the arbitrator\n\n" +
"Unjustified support tickets (e.g. caused by usability problems or questions) will " +
"cause a loss of the security deposit by the trader who opened the ticket.")
.actionButtonText("Open support ticket")
.onAction(model.dataModel::onOpenSupportTicket)
.closeButtonText("Cancel")
.onClose(() -> popup.hide())
.show();
}
};
}
@Override
protected void activate() {
sortedList = new SortedList<>(model.dataModel.list);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
scene = root.getScene();
if (scene != null) {
scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
/*appFocusSubscription = EasyBind.subscribe(scene.getWindow().focusedProperty(), isFocused -> {
if (isFocused && model.dataModel.selectedItemProperty.get() != null) {
// Focus selectedItem from model
int index = table.getItems().indexOf(model.dataModel.selectedItemProperty.get());
UserThread.execute(() -> {
//TODO app wide focus
//table.requestFocus();
//UserThread.execute(() -> table.getFocusModel().focus(index));
});
}
});*/
}
selectedItemSubscription = EasyBind.subscribe(model.dataModel.selectedItemProperty, selectedItem -> {
if (selectedItem != null) {
if (selectedSubView != null)
selectedSubView.deactivate();
if (selectedItem.getTrade() != null) {
selectedSubView = model.dataModel.tradeManager.isBuyer(model.dataModel.getOffer()) ?
new BuyerSubView(model) : new SellerSubView(model);
selectedSubView.setMinHeight(430);
VBox.setVgrow(selectedSubView, Priority.ALWAYS);
if (root.getChildren().size() == 1)
root.getChildren().add(selectedSubView);
else if (root.getChildren().size() == 2)
root.getChildren().set(1, selectedSubView);
}
updateTableSelection();
} else {
removeSelectedSubView();
}
model.onSelectedItemChanged(selectedItem);
if (selectedSubView != null && selectedItem != null)
selectedSubView.activate();
});
selectedTableItemSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(),
selectedItem -> {
if (selectedItem != null && !selectedItem.equals(model.dataModel.selectedItemProperty.get()))
model.dataModel.onSelectItem(selectedItem);
});
updateTableSelection();
}
@Override
protected void deactivate() {
sortedList.comparatorProperty().unbind();
selectedItemSubscription.unsubscribe();
selectedTableItemSubscription.unsubscribe();
if (appFocusSubscription != null)
appFocusSubscription.unsubscribe();
removeSelectedSubView();
if (scene != null)
scene.removeEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler);
}
private void removeSelectedSubView() {
if (selectedSubView != null) {
selectedSubView.deactivate();
root.getChildren().remove(selectedSubView);
selectedSubView = null;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void updateTableSelection() {
PendingTradesListItem selectedItemFromModel = model.dataModel.selectedItemProperty.get();
if (selectedItemFromModel != null) {
// Select and focus selectedItem from model
int index = tableView.getItems().indexOf(selectedItemFromModel);
UserThread.execute(() -> {
//TODO app wide focus
tableView.getSelectionModel().select(index);
//table.requestFocus();
//UserThread.execute(() -> table.getFocusModel().focus(index));
});
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// CellFactories
///////////////////////////////////////////////////////////////////////////////////////////
private void setTradeIdColumnCellFactory() {
idColumn.setCellValueFactory((pendingTradesListItem) -> new ReadOnlyObjectWrapper<>(pendingTradesListItem.getValue()));
idColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem, PendingTradesListItem>>() {
@Override
public TableCell<PendingTradesListItem, PendingTradesListItem> call(TableColumn<PendingTradesListItem,
PendingTradesListItem> column) {
return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
private HyperlinkWithIcon field;
@Override
public void updateItem(final PendingTradesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
field = new HyperlinkWithIcon(item.getTrade().getShortId(), true);
field.setOnAction(event -> tradeDetailsWindow.show(item.getTrade()));
field.setTooltip(new Tooltip("Open popup for details"));
setGraphic(field);
} else {
setGraphic(null);
if (field != null)
field.setOnAction(null);
}
}
};
}
});
}
private void setDateColumnCellFactory() {
dateColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
dateColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem,
PendingTradesListItem>>() {
@Override
public TableCell<PendingTradesListItem, PendingTradesListItem> call(
TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
@Override
public void updateItem(final PendingTradesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
/* if (model.showDispute(item.getTrade())) {
setStyle("-fx-text-fill: -bs-error-red");
} else if (model.showWarning(item.getTrade())) {
setStyle("-fx-text-fill: -bs-warning");
} else {
setId("-fx-text-fill: black");
}*/
setText(formatter.formatDateTime(item.getTrade().getDate()));
} else {
setText(null);
}
}
};
}
});
}
private void setAmountColumnCellFactory() {
tradeAmountColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
tradeAmountColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem,
PendingTradesListItem>>() {
@Override
public TableCell<PendingTradesListItem, PendingTradesListItem> call(
TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
@Override
public void updateItem(final PendingTradesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty)
setText(formatter.formatCoinWithCode(item.getTrade().getTradeAmount()));
else
setText(null);
}
};
}
});
}
private void setPriceColumnCellFactory() {
priceColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
priceColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem,
PendingTradesListItem>>() {
@Override
public TableCell<PendingTradesListItem, PendingTradesListItem> call(
TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
@Override
public void updateItem(final PendingTradesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty)
setText(formatter.formatPrice(item.getPrice()));
else
setText(null);
}
};
}
});
}
private void setVolumeColumnCellFactory() {
tradeVolumeColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
tradeVolumeColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem,
PendingTradesListItem>>() {
@Override
public TableCell<PendingTradesListItem, PendingTradesListItem> call(
TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
@Override
public void updateItem(final PendingTradesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty)
setText(formatter.formatVolumeWithCode(item.getTrade().getTradeVolume()));
else
setText(null);
}
};
}
});
}
private void setPaymentMethodColumnCellFactory() {
paymentMethodColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
paymentMethodColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem,
PendingTradesListItem>>() {
@Override
public TableCell<PendingTradesListItem, PendingTradesListItem> call(
TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
@Override
public void updateItem(final PendingTradesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty)
setText(model.getPaymentMethod(item));
else
setText(null);
}
};
}
});
}
private void setMarketColumnCellFactory() {
marketColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
marketColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem,
PendingTradesListItem>>() {
@Override
public TableCell<PendingTradesListItem, PendingTradesListItem> call(
TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
@Override
public void updateItem(final PendingTradesListItem item, boolean empty) {
super.updateItem(item, empty);
setText(model.getMarketLabel(item));
}
};
}
});
}
private void setRoleColumnCellFactory() {
roleColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
roleColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem,
PendingTradesListItem>>() {
@Override
public TableCell<PendingTradesListItem, PendingTradesListItem> call(
TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
@Override
public void updateItem(final PendingTradesListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty)
setText(model.getMyRole(item));
else
setText(null);
}
};
}
});
}
private TableColumn<PendingTradesListItem, PendingTradesListItem> setAvatarColumnCellFactory() {
avatarColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue()));
avatarColumn.setCellFactory(
new Callback<TableColumn<PendingTradesListItem, PendingTradesListItem>, TableCell<PendingTradesListItem,
PendingTradesListItem>>() {
@Override
public TableCell<PendingTradesListItem, PendingTradesListItem> call(TableColumn<PendingTradesListItem, PendingTradesListItem> column) {
return new TableCell<PendingTradesListItem, PendingTradesListItem>() {
@Override
public void updateItem(final PendingTradesListItem newItem, boolean empty) {
super.updateItem(newItem, empty);
if (newItem != null && !empty && newItem.getTrade().getTradingPeerNodeAddress() != null) {
String hostName = newItem.getTrade().getTradingPeerNodeAddress().hostName;
int numPastTrades = model.getNumPastTrades(newItem.getTrade());
boolean hasTraded = numPastTrades > 0;
String tooltipText = hasTraded ? "Trading peers onion address: " + hostName + "\n" +
"You have already traded " + numPastTrades + " times with that peer." : "Trading peers onion address: " + hostName;
Node identIcon = new PeerInfoIcon(hostName, tooltipText, numPastTrades, privateNotificationManager, newItem.getTrade().getOffer());
setPadding(new Insets(-2, 0, -2, 0));
if (identIcon != null)
setGraphic(identIcon);
} else {
setGraphic(null);
}
}
};
}
});
return avatarColumn;
}
}