diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 9eb3417d60..4b8e5e2e95 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -219,7 +219,8 @@ shared.refundAgentForSupportStaff=Refund agent shared.delayedPayoutTxId=Delayed payout transaction ID shared.delayedPayoutTxReceiverAddress=Delayed payout transaction sent to shared.unconfirmedTransactionsLimitReached=You have too many unconfirmed transactions at the moment. Please try again later. - +shared.numItemsLabel=Number of entries: {0} +shared.filter=Filter #################################################################### # UI views @@ -1053,7 +1054,6 @@ funds.tx.dustAttackTx.popup=This transaction is sending a very small BTC amount To protect your privacy the Bisq wallet ignores such dust outputs for spending purposes and in the balance display. \ You can set the threshold amount when an output is considered dust in the settings. - #################################################################### # Support #################################################################### diff --git a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedListItem.java b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedListItem.java index fcafade802..a2d80382dc 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedListItem.java @@ -18,12 +18,13 @@ package bisq.desktop.main.funds.locked; import bisq.desktop.components.AutoTooltipLabel; +import bisq.desktop.util.DisplayUtils; import bisq.core.btc.listeners.BalanceListener; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.WalletService; -import bisq.core.trade.Tradable; +import bisq.core.locale.Res; import bisq.core.trade.Trade; import bisq.core.util.coin.CoinFormatter; @@ -33,21 +34,34 @@ import org.bitcoinj.core.Transaction; import javafx.scene.control.Label; +import lombok.Getter; + import javax.annotation.Nullable; class LockedListItem { private final BalanceListener balanceListener; - private final Label balanceLabel; - private final Trade trade; - private final AddressEntry addressEntry; private final BtcWalletService btcWalletService; private final CoinFormatter formatter; + + @Getter + private final Label balanceLabel; + @Getter + private final Trade trade; + @Getter + private final AddressEntry addressEntry; + @Getter private final String addressString; @Nullable private final Address address; + @Getter private Coin balance; + @Getter + private String balanceString; - public LockedListItem(Trade trade, AddressEntry addressEntry, BtcWalletService btcWalletService, CoinFormatter formatter) { + public LockedListItem(Trade trade, + AddressEntry addressEntry, + BtcWalletService btcWalletService, + CoinFormatter formatter) { this.trade = trade; this.addressEntry = addressEntry; this.btcWalletService = btcWalletService; @@ -55,15 +69,13 @@ class LockedListItem { if (trade.getDepositTx() != null && !trade.getDepositTx().getOutputs().isEmpty()) { address = WalletService.getAddressFromOutput(trade.getDepositTx().getOutput(0)); - addressString = address.toString(); + addressString = address != null ? address.toString() : ""; } else { address = null; addressString = ""; } - - // balance balanceLabel = new AutoTooltipLabel(); - balanceListener = new BalanceListener(getAddress()) { + balanceListener = new BalanceListener(address) { @Override public void onBalanceChanged(Coin balance, Transaction tx) { updateBalance(); @@ -73,38 +85,36 @@ class LockedListItem { updateBalance(); } + LockedListItem() { + this.trade = null; + this.addressEntry = null; + this.btcWalletService = null; + this.formatter = null; + addressString = null; + address = null; + balanceLabel = null; + balanceListener = null; + } + public void cleanup() { btcWalletService.removeBalanceListener(balanceListener); } private void updateBalance() { balance = addressEntry.getCoinLockedInMultiSigAsCoin(); - balanceLabel.setText(formatter.formatCoin(this.balance)); + balanceString = formatter.formatCoin(this.balance); + balanceLabel.setText(balanceString); } - @Nullable - private Address getAddress() { - return address; + public String getDetails() { + return trade != null ? + Res.get("funds.locked.locked", trade.getShortId()) : + Res.get("shared.noDetailsAvailable"); } - public AddressEntry getAddressEntry() { - return addressEntry; + public String getDateString() { + return trade != null ? + DisplayUtils.formatDateTime(trade.getDate()) : + Res.get("shared.noDateAvailable"); } - - public Label getBalanceLabel() { - return balanceLabel; - } - - public Coin getBalance() { - return balance; - } - - public String getAddressString() { - return addressString; - } - - public Tradable getTrade() { - return trade; - } - } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.fxml b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.fxml index a47194ae33..623c3e99f6 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.fxml @@ -17,8 +17,12 @@ ~ along with Bisq. If not, see . --> + + + + - + + diff --git a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.java b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.java index 1e8875b8bc..df3db4eadc 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/locked/LockedView.java @@ -19,12 +19,12 @@ package bisq.desktop.main.funds.locked; import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.AutoTooltipButton; import bisq.desktop.components.AutoTooltipLabel; import bisq.desktop.components.ExternalHyperlink; import bisq.desktop.components.HyperlinkWithIcon; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; import bisq.desktop.main.overlays.windows.TradeDetailsWindow; -import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.GUIUtil; import bisq.core.btc.listeners.BalanceListener; @@ -43,6 +43,8 @@ import bisq.core.util.coin.CoinFormatter; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; +import com.googlecode.jcsv.writer.CSVEntryConverter; + import javax.inject.Inject; import javax.inject.Named; @@ -50,12 +52,20 @@ import de.jensd.fx.fontawesome.AwesomeIcon; import javafx.fxml.FXML; +import javafx.stage.Stage; + +import javafx.scene.control.Label; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.Tooltip; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.VBox; +import javafx.geometry.Insets; + import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; @@ -77,6 +87,12 @@ public class LockedView extends ActivatableView { TableView tableView; @FXML TableColumn dateColumn, detailsColumn, addressColumn, balanceColumn; + @FXML + Label numItems; + @FXML + Region spacer; + @FXML + AutoTooltipButton exportButton; private final BtcWalletService btcWalletService; private final TradeManager tradeManager; @@ -97,8 +113,13 @@ public class LockedView extends ActivatableView { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - private LockedView(BtcWalletService btcWalletService, TradeManager tradeManager, OpenOfferManager openOfferManager, Preferences preferences, - @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) { + private LockedView(BtcWalletService btcWalletService, + TradeManager tradeManager, + OpenOfferManager openOfferManager, + Preferences preferences, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, + OfferDetailsWindow offerDetailsWindow, + TradeDetailsWindow tradeDetailsWindow) { this.btcWalletService = btcWalletService; this.tradeManager = tradeManager; this.openOfferManager = openOfferManager; @@ -138,6 +159,10 @@ public class LockedView extends ActivatableView { }; openOfferListChangeListener = c -> updateList(); tradeListChangeListener = c -> updateList(); + + HBox.setHgrow(spacer, Priority.ALWAYS); + numItems.setPadding(new Insets(-5, 0, 0, 10)); + exportButton.updateText(Res.get("shared.exportCSV")); } @Override @@ -149,6 +174,33 @@ public class LockedView extends ActivatableView { updateList(); btcWalletService.addBalanceListener(balanceListener); + + numItems.setText(Res.get("shared.numItemsLabel", sortedList.size())); + exportButton.setOnAction(event -> { + ObservableList> tableColumns = tableView.getColumns(); + int reportColumns = tableColumns.size(); + CSVEntryConverter headerConverter = transactionsListItem -> { + String[] columns = new String[reportColumns]; + for (int i = 0; i < columns.length; i++) + columns[i] = ((AutoTooltipLabel) tableColumns.get(i).getGraphic()).getText(); + return columns; + }; + CSVEntryConverter contentConverter = item -> { + String[] columns = new String[reportColumns]; + columns[0] = item.getDateString(); + columns[1] = item.getDetails(); + columns[2] = item.getAddressString(); + columns[3] = item.getBalanceString(); + return columns; + }; + + GUIUtil.exportCSV("lockedInTradesFunds.csv", + headerConverter, + contentConverter, + new LockedListItem(), + sortedList, + (Stage) root.getScene().getWindow()); + }); } @Override @@ -158,6 +210,7 @@ public class LockedView extends ActivatableView { sortedList.comparatorProperty().unbind(); observableList.forEach(LockedListItem::cleanup); btcWalletService.removeBalanceListener(balanceListener); + exportButton.setOnAction(null); } @@ -169,7 +222,8 @@ public class LockedView extends ActivatableView { observableList.forEach(LockedListItem::cleanup); observableList.setAll(tradeManager.getTradesStreamWithFundsLockedIn() .map(trade -> { - final Optional addressEntryOptional = btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG); + Optional addressEntryOptional = btcWalletService.getAddressEntry(trade.getId(), + AddressEntry.Context.MULTI_SIG); return addressEntryOptional.map(addressEntry -> new LockedListItem(trade, addressEntry, btcWalletService, @@ -227,9 +281,9 @@ public class LockedView extends ActivatableView { super.updateItem(item, empty); if (item != null && !empty) { if (getTradable(item).isPresent()) - setGraphic(new AutoTooltipLabel(DisplayUtils.formatDateTime(getTradable(item).get().getDate()))); + setGraphic(new AutoTooltipLabel(item.getDateString())); else - setGraphic(new AutoTooltipLabel(Res.get("shared.noDateAvailable"))); + setGraphic(new AutoTooltipLabel(item.getDateString())); } else { setGraphic(null); } @@ -257,13 +311,13 @@ public class LockedView extends ActivatableView { if (item != null && !empty) { Optional tradableOptional = getTradable(item); if (tradableOptional.isPresent()) { - field = new HyperlinkWithIcon(Res.get("funds.locked.locked", item.getTrade().getShortId()), + field = new HyperlinkWithIcon(item.getDetails(), AwesomeIcon.INFO_SIGN); field.setOnAction(event -> openDetailPopup(item)); field.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails"))); setGraphic(field); } else { - setGraphic(new AutoTooltipLabel(Res.get("shared.noDetailsAvailable"))); + setGraphic(new AutoTooltipLabel(item.getDetails())); } } else { diff --git a/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedListItem.java b/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedListItem.java index 4b36ce9f12..04d7ad75a2 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedListItem.java @@ -18,12 +18,13 @@ package bisq.desktop.main.funds.reserved; import bisq.desktop.components.AutoTooltipLabel; +import bisq.desktop.util.DisplayUtils; import bisq.core.btc.listeners.BalanceListener; import bisq.core.btc.model.AddressEntry; import bisq.core.btc.wallet.BtcWalletService; +import bisq.core.locale.Res; import bisq.core.offer.OpenOffer; -import bisq.core.trade.Tradable; import bisq.core.util.coin.CoinFormatter; import org.bitcoinj.core.Address; @@ -34,26 +35,40 @@ import javafx.scene.control.Label; import java.util.Optional; +import lombok.Getter; + class ReservedListItem { private final BalanceListener balanceListener; - private final Label balanceLabel; - private final OpenOffer openOffer; - private final AddressEntry addressEntry; private final BtcWalletService btcWalletService; private final CoinFormatter formatter; - private final String addressString; - private Coin balance; - public ReservedListItem(OpenOffer openOffer, AddressEntry addressEntry, BtcWalletService btcWalletService, CoinFormatter formatter) { + @Getter + private final Label balanceLabel; + @Getter + private final OpenOffer openOffer; + @Getter + private final AddressEntry addressEntry; + @Getter + private final String addressString; + @Getter + private final Address address; + @Getter + private Coin balance; + @Getter + private String balanceString; + + public ReservedListItem(OpenOffer openOffer, + AddressEntry addressEntry, + BtcWalletService btcWalletService, + CoinFormatter formatter) { this.openOffer = openOffer; this.addressEntry = addressEntry; this.btcWalletService = btcWalletService; this.formatter = formatter; addressString = addressEntry.getAddressString(); - - // balance + address = addressEntry.getAddress(); balanceLabel = new AutoTooltipLabel(); - balanceListener = new BalanceListener(getAddress()) { + balanceListener = new BalanceListener(address) { @Override public void onBalanceChanged(Coin balance, Transaction tx) { updateBalance(); @@ -63,41 +78,40 @@ class ReservedListItem { updateBalance(); } + ReservedListItem() { + this.openOffer = null; + this.addressEntry = null; + this.btcWalletService = null; + this.formatter = null; + addressString = null; + address = null; + balanceLabel = null; + balanceListener = null; + } + public void cleanup() { btcWalletService.removeBalanceListener(balanceListener); } private void updateBalance() { - final Optional addressEntryOptional = btcWalletService.getAddressEntry(openOffer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE); + Optional addressEntryOptional = btcWalletService.getAddressEntry(openOffer.getId(), + AddressEntry.Context.RESERVED_FOR_TRADE); addressEntryOptional.ifPresent(addressEntry -> { balance = btcWalletService.getBalanceForAddress(addressEntry.getAddress()); - if (balance != null) - balanceLabel.setText(formatter.formatCoin(balance)); + if (balance != null) { + balanceString = formatter.formatCoin(balance); + balanceLabel.setText(balanceString); + } }); } - private Address getAddress() { - return addressEntry.getAddress(); + public String getDateAsString() { + return DisplayUtils.formatDateTime(openOffer.getDate()); } - public AddressEntry getAddressEntry() { - return addressEntry; + public String getDetails() { + return openOffer != null ? + Res.get("funds.reserved.reserved", openOffer.getShortId()) : + Res.get("shared.noDetailsAvailable"); } - - public Label getBalanceLabel() { - return balanceLabel; - } - - public Coin getBalance() { - return balance; - } - - public String getAddressString() { - return addressString; - } - - public Tradable getOpenOffer() { - return openOffer; - } - } diff --git a/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.fxml b/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.fxml index 59cec585db..8a43556b99 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.fxml @@ -17,8 +17,12 @@ ~ along with Bisq. If not, see . --> + + + + - + + diff --git a/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.java b/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.java index 061ed666e3..cac62e6c44 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/reserved/ReservedView.java @@ -19,12 +19,12 @@ package bisq.desktop.main.funds.reserved; import bisq.desktop.common.view.ActivatableView; import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.AutoTooltipButton; import bisq.desktop.components.AutoTooltipLabel; import bisq.desktop.components.ExternalHyperlink; import bisq.desktop.components.HyperlinkWithIcon; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; import bisq.desktop.main.overlays.windows.TradeDetailsWindow; -import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.GUIUtil; import bisq.core.btc.listeners.BalanceListener; @@ -43,6 +43,8 @@ import bisq.core.util.coin.CoinFormatter; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; +import com.googlecode.jcsv.writer.CSVEntryConverter; + import javax.inject.Inject; import javax.inject.Named; @@ -50,12 +52,20 @@ import de.jensd.fx.fontawesome.AwesomeIcon; import javafx.fxml.FXML; +import javafx.stage.Stage; + +import javafx.scene.control.Label; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.Tooltip; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.VBox; +import javafx.geometry.Insets; + import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; @@ -77,6 +87,12 @@ public class ReservedView extends ActivatableView { TableView tableView; @FXML TableColumn dateColumn, detailsColumn, addressColumn, balanceColumn; + @FXML + Label numItems; + @FXML + Region spacer; + @FXML + AutoTooltipButton exportButton; private final BtcWalletService btcWalletService; private final TradeManager tradeManager; @@ -97,8 +113,13 @@ public class ReservedView extends ActivatableView { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - private ReservedView(BtcWalletService btcWalletService, TradeManager tradeManager, OpenOfferManager openOfferManager, Preferences preferences, - @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) { + private ReservedView(BtcWalletService btcWalletService, + TradeManager tradeManager, + OpenOfferManager openOfferManager, + Preferences preferences, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, + OfferDetailsWindow offerDetailsWindow, + TradeDetailsWindow tradeDetailsWindow) { this.btcWalletService = btcWalletService; this.tradeManager = tradeManager; this.openOfferManager = openOfferManager; @@ -138,6 +159,10 @@ public class ReservedView extends ActivatableView { }; openOfferListChangeListener = c -> updateList(); tradeListChangeListener = c -> updateList(); + + HBox.setHgrow(spacer, Priority.ALWAYS); + numItems.setPadding(new Insets(-5, 0, 0, 10)); + exportButton.updateText(Res.get("shared.exportCSV")); } @Override @@ -149,6 +174,33 @@ public class ReservedView extends ActivatableView { updateList(); btcWalletService.addBalanceListener(balanceListener); + + numItems.setText(Res.get("shared.numItemsLabel", sortedList.size())); + exportButton.setOnAction(event -> { + ObservableList> tableColumns = tableView.getColumns(); + int reportColumns = tableColumns.size(); + CSVEntryConverter headerConverter = transactionsListItem -> { + String[] columns = new String[reportColumns]; + for (int i = 0; i < columns.length; i++) + columns[i] = ((AutoTooltipLabel) tableColumns.get(i).getGraphic()).getText(); + return columns; + }; + CSVEntryConverter contentConverter = item -> { + String[] columns = new String[reportColumns]; + columns[0] = item.getDateAsString(); + columns[1] = item.getDetails(); + columns[2] = item.getAddressString(); + columns[3] = item.getBalanceString(); + return columns; + }; + + GUIUtil.exportCSV("reservedInOffersFunds.csv", + headerConverter, + contentConverter, + new ReservedListItem(), + sortedList, + (Stage) root.getScene().getWindow()); + }); } @Override @@ -158,6 +210,7 @@ public class ReservedView extends ActivatableView { sortedList.comparatorProperty().unbind(); observableList.forEach(ReservedListItem::cleanup); btcWalletService.removeBalanceListener(balanceListener); + exportButton.setOnAction(null); } @@ -226,9 +279,9 @@ public class ReservedView extends ActivatableView { public void updateItem(final ReservedListItem item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { - if (getTradable(item).isPresent()) - setGraphic(new AutoTooltipLabel(DisplayUtils.formatDateTime(getTradable(item).get().getDate()))); - else + if (getTradable(item).isPresent()) { + setGraphic(new AutoTooltipLabel(item.getDateAsString())); + } else setGraphic(new AutoTooltipLabel(Res.get("shared.noDateAvailable"))); } else { setGraphic(null); @@ -257,13 +310,12 @@ public class ReservedView extends ActivatableView { if (item != null && !empty) { Optional tradableOptional = getTradable(item); if (tradableOptional.isPresent()) { - field = new HyperlinkWithIcon(Res.get("funds.reserved.reserved", item.getOpenOffer().getShortId()), - AwesomeIcon.INFO_SIGN); + field = new HyperlinkWithIcon(item.getDetails(), AwesomeIcon.INFO_SIGN); field.setOnAction(event -> openDetailPopup(item)); field.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails"))); setGraphic(field); } else { - setGraphic(new AutoTooltipLabel(Res.get("shared.noDetailsAvailable"))); + setGraphic(new AutoTooltipLabel(item.getDetails())); } } else { diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsView.fxml b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsView.fxml index dde9599231..b2554dfca6 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsView.fxml @@ -18,8 +18,11 @@ --> + + + - - + + - + + diff --git a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsView.java b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsView.java index 6fd38c1214..3020828aa1 100644 --- a/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsView.java +++ b/desktop/src/main/java/bisq/desktop/main/funds/transactions/TransactionsView.java @@ -56,14 +56,20 @@ import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.Tooltip; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.VBox; +import javafx.geometry.Insets; + import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.event.EventHandler; @@ -80,11 +86,16 @@ import javax.annotation.Nullable; @FxmlView public class TransactionsView extends ActivatableView { + @FXML TableView tableView; @FXML TableColumn dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, memoColumn, confidenceColumn, revertTxColumn; @FXML + Label numItems; + @FXML + Region spacer; + @FXML AutoTooltipButton exportButton; private final DisplayedTransactions displayedTransactions; @@ -180,6 +191,8 @@ public class TransactionsView extends ActivatableView { } }; + HBox.setHgrow(spacer, Priority.ALWAYS); + numItems.setPadding(new Insets(-5, 0, 0, 10)); exportButton.updateText(Res.get("shared.exportCSV")); } @@ -195,6 +208,7 @@ public class TransactionsView extends ActivatableView { if (scene != null) scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler); + numItems.setText(Res.get("shared.numItemsLabel", sortedDisplayedTransactions.size())); exportButton.setOnAction(event -> { final ObservableList> tableColumns = tableView.getColumns(); final int reportColumns = tableColumns.size() - 1; // CSV report excludes the last column (an icon) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.fxml b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.fxml index 1285d7e588..79f11c770e 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.fxml @@ -20,10 +20,12 @@ + + + + + + + + @@ -50,10 +58,10 @@ - - - - + + + diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java index 1c79620d9f..8d5284da62 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/closedtrades/ClosedTradesView.java @@ -53,6 +53,7 @@ import javafx.fxml.FXML; import javafx.stage.Stage; import javafx.scene.Node; +import javafx.scene.control.Label; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; @@ -60,6 +61,7 @@ import javafx.scene.control.Tooltip; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.geometry.Insets; @@ -114,15 +116,19 @@ public class ClosedTradesView extends ActivatableViewAndModel applyFilteredListPredicate(filterTextField.getText()); - footerBox.setSpacing(5); - HBox.setHgrow(spacer, Priority.ALWAYS); + searchBox.setSpacing(5); + HBox.setHgrow(searchBoxSpacer, Priority.ALWAYS); exportButton.updateText(Res.get("shared.exportCSV")); HBox.setMargin(exportButton, new Insets(0, 10, 0, 0)); + + HBox.setHgrow(footerSpacer, Priority.ALWAYS); + numItems.setPadding(new Insets(-5, 0, 0, 10)); + exportButton.updateText(Res.get("shared.exportCSV")); } @Override @@ -244,6 +253,7 @@ public class ClosedTradesView extends ActivatableViewAndModel { final ObservableList> tableColumns = tableView.getColumns(); CSVEntryConverter headerConverter = transactionsListItem -> { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesListItem.java b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesListItem.java index 3d10b043f4..f16365e1d2 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesListItem.java @@ -19,18 +19,17 @@ package bisq.desktop.main.portfolio.failedtrades; import bisq.core.trade.Trade; -/** - * We could remove that wrapper if it is not needed for additional UI only fields. - */ -class FailedTradesListItem { +import lombok.Getter; +class FailedTradesListItem { + @Getter private final Trade trade; FailedTradesListItem(Trade trade) { this.trade = trade; } - Trade getTrade() { - return trade; + FailedTradesListItem() { + this.trade = null; } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesView.fxml b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesView.fxml index 840a022f27..2ea03ddeee 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesView.fxml @@ -17,8 +17,12 @@ ~ along with Bisq. If not, see . --> + + + + + + diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesView.java index 9d427d3da7..89e1986913 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/failedtrades/FailedTradesView.java @@ -19,11 +19,13 @@ package bisq.desktop.main.portfolio.failedtrades; import bisq.desktop.common.view.ActivatableViewAndModel; import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.AutoTooltipButton; import bisq.desktop.components.AutoTooltipLabel; import bisq.desktop.components.HyperlinkWithIcon; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.TradeDetailsWindow; import bisq.desktop.util.FormBuilder; +import bisq.desktop.util.GUIUtil; import bisq.core.locale.Res; import bisq.core.trade.Trade; @@ -31,6 +33,8 @@ import bisq.core.trade.Trade; import bisq.common.config.Config; import bisq.common.util.Utilities; +import com.googlecode.jcsv.writer.CSVEntryConverter; + import javax.inject.Inject; import javax.inject.Named; @@ -40,6 +44,8 @@ import com.jfoenix.controls.JFXButton; import javafx.fxml.FXML; +import javafx.stage.Stage; + import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.control.TableCell; @@ -48,12 +54,18 @@ import javafx.scene.control.TableView; import javafx.scene.control.Tooltip; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.VBox; +import javafx.geometry.Insets; + import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.event.EventHandler; +import javafx.collections.ObservableList; import javafx.collections.transformation.SortedList; import javafx.util.Callback; @@ -68,6 +80,13 @@ public class FailedTradesView extends ActivatableViewAndModel priceColumn, amountColumn, volumeColumn, marketColumn, directionColumn, dateColumn, tradeIdColumn, stateColumn, removeTradeColumn; + @FXML + Label numItems; + @FXML + Region spacer; + @FXML + AutoTooltipButton exportButton; + private final TradeDetailsWindow tradeDetailsWindow; private SortedList sortedList; private EventHandler keyEventEventHandler; @@ -137,6 +156,10 @@ public class FailedTradesView extends ActivatableViewAndModel(model.getList()); sortedList.comparatorProperty().bind(tableView.comparatorProperty()); tableView.setItems(sortedList); + + numItems.setText(Res.get("shared.numItemsLabel", sortedList.size())); + exportButton.setOnAction(event -> { + ObservableList> tableColumns = tableView.getColumns(); + int reportColumns = tableColumns.size() - 1; // CSV report excludes the last column (an icon) + CSVEntryConverter headerConverter = transactionsListItem -> { + String[] columns = new String[reportColumns]; + for (int i = 0; i < columns.length; i++) + columns[i] = ((AutoTooltipLabel) tableColumns.get(i).getGraphic()).getText(); + return columns; + }; + CSVEntryConverter contentConverter = item -> { + String[] columns = new String[reportColumns]; + columns[0] = model.getTradeId(item); + columns[1] = model.getDate(item); + columns[2] = model.getMarketLabel(item); + columns[3] = model.getPrice(item); + columns[4] = model.getAmount(item); + columns[5] = model.getVolume(item); + columns[6] = model.getDirectionLabel(item); + columns[7] = model.getState(item); + return columns; + }; + + GUIUtil.exportCSV("failedTrades.csv", + headerConverter, + contentConverter, + new FailedTradesListItem(), + sortedList, + (Stage) root.getScene().getWindow()); + }); } @Override diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOfferListItem.java b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOfferListItem.java index b6dec35040..6640f2b0d1 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOfferListItem.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOfferListItem.java @@ -20,19 +20,21 @@ package bisq.desktop.main.portfolio.openoffer; import bisq.core.offer.Offer; import bisq.core.offer.OpenOffer; +import lombok.Getter; + /** * We could remove that wrapper if it is not needed for additional UI only fields. */ class OpenOfferListItem { - + @Getter private final OpenOffer openOffer; - public OpenOfferListItem(OpenOffer openOffer) { + OpenOfferListItem(OpenOffer openOffer) { this.openOffer = openOffer; } - public OpenOffer getOpenOffer() { - return openOffer; + OpenOfferListItem() { + openOffer = null; } public Offer getOffer() { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.fxml b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.fxml index 71deb94aaa..d47a52824a 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.fxml +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.fxml @@ -17,8 +17,12 @@ ~ along with Bisq. If not, see . --> + + + + - + + diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java index a1328c8435..3208c7078b 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersView.java @@ -20,6 +20,7 @@ package bisq.desktop.main.portfolio.openoffer; import bisq.desktop.Navigation; import bisq.desktop.common.view.ActivatableViewAndModel; import bisq.desktop.common.view.FxmlView; +import bisq.desktop.components.AutoTooltipButton; import bisq.desktop.components.AutoTooltipCheckBox; import bisq.desktop.components.AutoTooltipLabel; import bisq.desktop.components.AutoTooltipTableColumn; @@ -30,28 +31,42 @@ import bisq.desktop.main.funds.withdrawal.WithdrawalView; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.OfferDetailsWindow; import bisq.desktop.main.portfolio.PortfolioView; +import bisq.desktop.util.GUIUtil; import bisq.core.locale.Res; import bisq.core.offer.OpenOffer; import bisq.core.user.DontShowAgainLookup; +import com.googlecode.jcsv.writer.CSVEntryConverter; + import javax.inject.Inject; import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; import javafx.fxml.FXML; +import javafx.stage.Stage; + +import javafx.scene.Node; +import javafx.scene.Parent; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.Tooltip; import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.VBox; +import javafx.geometry.Insets; + import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.ObservableList; import javafx.collections.transformation.SortedList; import javafx.util.Callback; @@ -71,6 +86,13 @@ public class OpenOffersView extends ActivatableViewAndModel priceColumn, deviationColumn, amountColumn, volumeColumn, marketColumn, directionColumn, dateColumn, offerIdColumn, deactivateItemColumn, removeItemColumn, editItemColumn, paymentMethodColumn; + @FXML + Label numItems; + @FXML + Region spacer; + @FXML + AutoTooltipButton exportButton; + private final Navigation navigation; private final OfferDetailsWindow offerDetailsWindow; private SortedList sortedList; @@ -121,7 +143,7 @@ public class OpenOffersView extends ActivatableViewAndModel o.getOffer().getAmount())); priceColumn.setComparator(Comparator.comparing(o -> o.getOffer().getPrice(), Comparator.nullsFirst(Comparator.naturalOrder()))); deviationColumn.setComparator(Comparator.comparing(o -> - o.getOffer().isUseMarketBasedPrice() ? o.getOffer().getMarketPriceMargin() : 1, + o.getOffer().isUseMarketBasedPrice() ? o.getOffer().getMarketPriceMargin() : 1, Comparator.nullsFirst(Comparator.naturalOrder()))); volumeColumn.setComparator(Comparator.comparing(o -> o.getOffer().getVolume(), Comparator.nullsFirst(Comparator.naturalOrder()))); dateColumn.setComparator(Comparator.comparing(o -> o.getOffer().getDate())); @@ -129,6 +151,10 @@ public class OpenOffersView extends ActivatableViewAndModel(model.getList()); sortedList.comparatorProperty().bind(tableView.comparatorProperty()); tableView.setItems(sortedList); + + numItems.setText(Res.get("shared.numItemsLabel", sortedList.size())); + exportButton.setOnAction(event -> { + ObservableList> tableColumns = tableView.getColumns(); + int reportColumns = tableColumns.size() - 2; // CSV report excludes the last columns (icons) + CSVEntryConverter headerConverter = transactionsListItem -> { + String[] columns = new String[reportColumns]; + for (int i = 0; i < columns.length; i++) { + Node graphic = tableColumns.get(i).getGraphic(); + if (graphic instanceof AutoTooltipLabel) { + columns[i] = ((AutoTooltipLabel) graphic).getText(); + } else if (graphic instanceof HBox) { + // Deviation has a Hbox with AutoTooltipLabel as first child in header + columns[i] = ((AutoTooltipLabel) ((Parent) graphic).getChildrenUnmodifiable().get(0)).getText(); + } else { + // Not expected + columns[i] = "N/A"; + } + } + return columns; + }; + CSVEntryConverter contentConverter = item -> { + String[] columns = new String[reportColumns]; + columns[0] = model.getOfferId(item); + columns[1] = model.getDate(item); + columns[2] = model.getMarketLabel(item); + columns[3] = model.getPrice(item); + columns[4] = model.getPriceDeviation(item); + columns[5] = model.getAmount(item); + columns[6] = model.getVolume(item); + columns[7] = model.getPaymentMethod(item); + columns[8] = model.getDirectionLabel(item); + columns[9] = String.valueOf(!item.getOpenOffer().isDeactivated()); + return columns; + }; + + GUIUtil.exportCSV("openOffers.csv", + headerConverter, + contentConverter, + new OpenOfferListItem(), + sortedList, + (Stage) root.getScene().getWindow()); + }); } @Override @@ -226,7 +295,7 @@ public class OpenOffersView extends ActivatableViewAndModel offerDetailsWindow.show(item.getOffer())); field.setTooltip(new Tooltip(Res.get("tooltip.openPopupForDetails"))); setGraphic(field); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersViewModel.java index 94b4533935..709ba4ee84 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/openoffer/OpenOffersViewModel.java @@ -27,8 +27,8 @@ import bisq.core.locale.Res; import bisq.core.monetary.Price; import bisq.core.offer.Offer; import bisq.core.offer.OpenOffer; -import bisq.core.util.coin.BsqFormatter; import bisq.core.util.FormattingUtils; +import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; import bisq.network.p2p.P2PService; @@ -78,7 +78,7 @@ class OpenOffersViewModel extends ActivatableWithDataModel return dataModel.getList(); } - String getTradeId(OpenOfferListItem item) { + String getOfferId(OpenOfferListItem item) { return item.getOffer().getShortId(); }