diff --git a/explorer/src/main/kotlin/com/r3corda/explorer/ui/TableViewUtilities.kt b/explorer/src/main/kotlin/com/r3corda/explorer/ui/TableViewUtilities.kt index b877c81596..b4887f5099 100644 --- a/explorer/src/main/kotlin/com/r3corda/explorer/ui/TableViewUtilities.kt +++ b/explorer/src/main/kotlin/com/r3corda/explorer/ui/TableViewUtilities.kt @@ -3,6 +3,8 @@ package com.r3corda.explorer.ui import com.r3corda.explorer.formatters.Formatter import javafx.beans.binding.Bindings import javafx.beans.value.ObservableValue +import javafx.scene.Node +import javafx.scene.control.ListCell import javafx.scene.control.TableCell import javafx.scene.control.TableColumn import javafx.scene.control.TableView @@ -54,3 +56,21 @@ fun <S> TableView<S>.singleRowSelection() = Bindings.createObjectBinding({ SingleRowSelection.Selected(selectionModel.selectedItems[0]) } }, arrayOf(selectionModel.selectedItems)) + +fun <S, T> TableColumn<S, T>.setCustomCellFactory(toNode: (T) -> Node) { + setCellFactory { + object : TableCell<S, T>() { + init { + text = null + } + override fun updateItem(value: T?, empty: Boolean) { + super.updateItem(value, empty) + graphic = if (value != null && !empty) { + toNode(value) + } else { + null + } + } + } + } +} diff --git a/explorer/src/main/kotlin/com/r3corda/explorer/views/CashViewer.kt b/explorer/src/main/kotlin/com/r3corda/explorer/views/CashViewer.kt index e1cc4546b1..976e409cd2 100644 --- a/explorer/src/main/kotlin/com/r3corda/explorer/views/CashViewer.kt +++ b/explorer/src/main/kotlin/com/r3corda/explorer/views/CashViewer.kt @@ -15,8 +15,6 @@ import com.r3corda.explorer.model.ReportingCurrencyModel import com.r3corda.explorer.model.SettingsModel import com.r3corda.explorer.ui.* import javafx.beans.binding.Bindings -import javafx.beans.property.ReadOnlyObjectWrapper -import javafx.beans.property.ReadOnlyStringWrapper import javafx.beans.value.ObservableValue import javafx.collections.FXCollections import javafx.collections.ObservableList @@ -323,16 +321,16 @@ class CashViewer : View() { cashViewerTableIssuerCurrency.setCellValueFactory { val node = it.value.value when (node) { - ViewerNode.Root -> ReadOnlyStringWrapper("") - is ViewerNode.IssuerNode -> ReadOnlyStringWrapper(node.issuer.toString()) + ViewerNode.Root -> "".lift() + is ViewerNode.IssuerNode -> node.issuer.toString().lift() is ViewerNode.CurrencyNode -> node.amount.map { it.token.toString() } } } cashViewerTableLocalCurrency.setCellValueFactory { val node = it.value.value when (node) { - ViewerNode.Root -> ReadOnlyObjectWrapper(null) - is ViewerNode.IssuerNode -> ReadOnlyObjectWrapper(null) + ViewerNode.Root -> null.lift() + is ViewerNode.IssuerNode -> null.lift() is ViewerNode.CurrencyNode -> node.amount.map { it } } } @@ -344,7 +342,7 @@ class CashViewer : View() { cashViewerTableEquiv.setCellValueFactory { val node = it.value.value when (node) { - ViewerNode.Root -> ReadOnlyObjectWrapper(null) + ViewerNode.Root -> null.lift() is ViewerNode.IssuerNode -> node.sumEquivAmount.map { it } is ViewerNode.CurrencyNode -> node.equivAmount.map { it } } diff --git a/explorer/src/main/kotlin/com/r3corda/explorer/views/TransactionViewer.kt b/explorer/src/main/kotlin/com/r3corda/explorer/views/TransactionViewer.kt index b4490f6ae1..c074e04fad 100644 --- a/explorer/src/main/kotlin/com/r3corda/explorer/views/TransactionViewer.kt +++ b/explorer/src/main/kotlin/com/r3corda/explorer/views/TransactionViewer.kt @@ -1,11 +1,8 @@ package com.r3corda.explorer.views -import com.r3corda.client.fxutils.ChosenList +import com.r3corda.client.fxutils.* import com.r3corda.client.model.* import com.r3corda.contracts.asset.Cash -import com.r3corda.core.contracts.Amount -import com.r3corda.core.contracts.CommandData -import com.r3corda.core.contracts.withoutIssuer import com.r3corda.core.contracts.* import com.r3corda.core.crypto.Party import com.r3corda.core.crypto.SecureHash @@ -21,7 +18,6 @@ import com.r3corda.explorer.sign import com.r3corda.explorer.ui.* import com.r3corda.node.services.monitor.ServiceToClientEvent import javafx.beans.binding.Bindings -import javafx.beans.property.ReadOnlyObjectWrapper import javafx.beans.value.ObservableValue import javafx.collections.FXCollections import javafx.collections.ObservableList @@ -123,15 +119,15 @@ class TransactionViewer: View() { /** * We map the gathered data about transactions almost one-to-one to the nodes. */ - private val viewerNodes = EasyBind.map(gatheredTransactionDataList) { + private val viewerNodes = gatheredTransactionDataList.map { ViewerNode( - transactionId = EasyBind.map(it.transaction) { it?.id }, + transactionId = it.transaction.map { it?.id }, fiberId = it.fiberId, clientUuid = it.uuid, /** * We can't really do any better based on uuid, we need to store explicit data for this TODO */ - originator = EasyBind.map(it.uuid) { uuid -> + originator = it.uuid.map { uuid -> if (uuid == null) { "Someone" } else { @@ -142,57 +138,31 @@ class TransactionViewer: View() { protocolStatus = it.protocolStatus, stateMachineStatus = it.stateMachineStatus, statusUpdated = it.lastUpdate, - commandTypes = EasyBind.map(it.transaction) { + commandTypes = it.transaction.map { val commands = mutableSetOf<Class<CommandData>>() it?.commands?.forEach { commands.add(it.value.javaClass) } commands }, - totalValueEquiv = EasyBind.combine(myIdentity, reportingExchange, it.transaction) { identity, exchange, transaction -> - transaction?.let { calculateTotalEquiv(setOf(identity.owningKey), exchange.first, exchange.second, transaction) } - }, + totalValueEquiv = ::calculateTotalEquiv.lift(myIdentity, reportingExchange, it.transaction), transaction = it.transaction, allEvents = it.allEvents ) } - /** - * We calculate the total value by subtracting relevant input states and adding relevant output states, as long as they're cash - */ - private fun calculateTotalEquiv( - relevantPublicKeys: Set<PublicKey>, - reportingCurrency: Currency, - exchange: (Amount<Currency>) -> Amount<Currency>, - transaction: LedgerTransaction): AmountDiff<Currency> { - var sum = 0L - transaction.inputs.forEach { - val contractState = it.state.data - if (contractState is Cash.State && relevantPublicKeys.contains(contractState.owner)) { - sum -= exchange(contractState.amount.withoutIssuer()).quantity - } - } - transaction.outputs.forEach { - val contractState = it.data - if (contractState is Cash.State && relevantPublicKeys.contains(contractState.owner)) { - sum += exchange(contractState.amount.withoutIssuer()).quantity - } - } - return AmountDiff.fromLong(sum, reportingCurrency) - } - /** * The detail panes are only filled out if a transaction is selected */ private val selectedViewerNode = transactionViewTable.singleRowSelection() - private val selectedTransaction = EasyBind.monadic(selectedViewerNode).flatMap<LedgerTransaction?, SingleRowSelection<ViewerNode>> { + private val selectedTransaction = selectedViewerNode.bind { when (it) { - is SingleRowSelection.None -> ReadOnlyObjectWrapper(null) + is SingleRowSelection.None -> null.lift() is SingleRowSelection.Selected -> it.node.transaction } } - private val inputStateNodes = ChosenList<StateNode>(EasyBind.map(selectedTransaction) { + private val inputStateNodes = ChosenList(selectedTransaction.map { if (it == null) { FXCollections.emptyObservableList<StateNode>() } else { @@ -200,7 +170,7 @@ class TransactionViewer: View() { } }) - private val outputStateNodes = ChosenList<StateNode>(EasyBind.map(selectedTransaction) { + private val outputStateNodes = ChosenList(selectedTransaction.map { if (it == null) { FXCollections.emptyObservableList<StateNode>() } else { @@ -210,7 +180,7 @@ class TransactionViewer: View() { } }) - private val signatures = ChosenList<PublicKey>(EasyBind.map(selectedTransaction) { + private val signatures = ChosenList(selectedTransaction.map { if (it == null) { FXCollections.emptyObservableList<PublicKey>() } else { @@ -218,7 +188,7 @@ class TransactionViewer: View() { } }) - private val lowLevelEvents = ChosenList(EasyBind.map(selectedViewerNode) { + private val lowLevelEvents = ChosenList(selectedViewerNode.map { when (it) { is SingleRowSelection.None -> FXCollections.emptyObservableList<ServiceToClientEvent>() is SingleRowSelection.Selected -> it.node.allEvents @@ -237,8 +207,8 @@ class TransactionViewer: View() { private val onlyTransactionsTableShown = FXCollections.observableArrayList<Node>( transactionViewTable ) - private val topSplitPaneNodesShown = ChosenList<Node>( - EasyBind.map(selectedViewerNode) { selection -> + private val topSplitPaneNodesShown = ChosenList( + selectedViewerNode.map { selection -> if (selection is SingleRowSelection.None<*>) { onlyTransactionsTableShown } else { @@ -260,45 +230,45 @@ class TransactionViewer: View() { statesAmount: TableColumn<StateNode, Long>, statesEquiv: TableColumn<StateNode, Amount<Currency>> ) { - statesCountLabel.textProperty().bind(EasyBind.map(Bindings.size(states)) { "$it" }) + statesCountLabel.textProperty().bind(Bindings.size(states).map { "$it" }) Bindings.bindContent(statesTable.items, states) - statesId.setCellValueFactory { ReadOnlyObjectWrapper(it.value.stateRef.toString()) } - statesType.setCellValueFactory { ReadOnlyObjectWrapper(it.value.transactionState.data.javaClass) } + statesId.setCellValueFactory { it.value.stateRef.toString().lift() } + statesType.setCellValueFactory { it.value.transactionState.data.javaClass.lift() } statesOwner.setCellValueFactory { val state = it.value.transactionState.data if (state is OwnableState) { - ReadOnlyObjectWrapper(state.owner.toStringShort()) + state.owner.toStringShort().lift() } else { - ReadOnlyObjectWrapper("???") + "???".lift() } } statesLocalCurrency.setCellValueFactory { val state = it.value.transactionState.data if (state is Cash.State) { - ReadOnlyObjectWrapper(state.amount.token.product) + state.amount.token.product.lift() } else { - ReadOnlyObjectWrapper(null) + null.lift() } } statesAmount.setCellValueFactory { val state = it.value.transactionState.data if (state is Cash.State) { - ReadOnlyObjectWrapper(state.amount.quantity) + state.amount.quantity.lift() } else { - ReadOnlyObjectWrapper(null) + null.lift() } } statesAmount.setCellFactory(NumberFormatter.longComma.toTableCellFactory()) statesEquiv.setCellValueFactory { val state = it.value.transactionState.data if (state is Cash.State) { - EasyBind.map(reportingExchange) { exchange -> + reportingExchange.map { exchange -> exchange.second(state.amount.withoutIssuer()) } } else { - ReadOnlyObjectWrapper(null) + null.lift() } } } @@ -313,65 +283,37 @@ class TransactionViewer: View() { Math.floor(tableWidthWithoutPaddingAndBorder.toDouble() / transactionViewTable.columns.size).toInt() } - transactionViewTransactionId.setCellValueFactory { EasyBind.map (it.value.transactionId) { "${it ?: ""}" } } - transactionViewFiberId.setCellValueFactory { EasyBind.map (it.value.fiberId) { "${it?: ""}" } } - transactionViewClientUuid.setCellValueFactory { EasyBind.map (it.value.clientUuid) { "${it ?: ""}" } } - transactionViewProtocolStatus.setCellValueFactory { EasyBind.map(it.value.protocolStatus) { "${it ?: ""}" } } + transactionViewTransactionId.setCellValueFactory { it.value.transactionId.map { "${it ?: ""}" } } + transactionViewFiberId.setCellValueFactory { it.value.fiberId.map { "${it?: ""}" } } + transactionViewClientUuid.setCellValueFactory { it.value.clientUuid.map { "${it ?: ""}" } } + transactionViewProtocolStatus.setCellValueFactory { it.value.protocolStatus.map { "${it ?: ""}" } } transactionViewTransactionStatus.setCellValueFactory { it.value.transactionStatus } - // TODO reduce clutter - transactionViewTransactionStatus.setCellFactory { - object : TableCell<ViewerNode, TransactionCreateStatus?>() { - val label = Label() - override fun updateItem( - value: TransactionCreateStatus?, - empty: Boolean - ) { - super.updateItem(value, empty) - if (value == null || empty) { - graphic = null - text = null - } else { - graphic = label - val backgroundFill = when (value) { - is TransactionCreateStatus.Started -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY) - is TransactionCreateStatus.Failed -> BackgroundFill(Color.SALMON, CornerRadii.EMPTY, Insets.EMPTY) - null -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY) - } - label.background = Background(backgroundFill) - label.text = "$value" - } - } + transactionViewTransactionStatus.setCustomCellFactory { + val label = Label() + val backgroundFill = when (it) { + is TransactionCreateStatus.Started -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY) + is TransactionCreateStatus.Failed -> BackgroundFill(Color.SALMON, CornerRadii.EMPTY, Insets.EMPTY) + null -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY) } + label.background = Background(backgroundFill) + label.text = "$it" + label } - // TODO reduce clutter transactionViewStateMachineStatus.setCellValueFactory { it.value.stateMachineStatus } - transactionViewStateMachineStatus.setCellFactory { - object : TableCell<ViewerNode, StateMachineStatus?>() { - val label = Label() - override fun updateItem( - value: StateMachineStatus?, - empty: Boolean - ) { - super.updateItem(value, empty) - if (value == null || empty) { - graphic = null - text = null - } else { - graphic = label - val backgroundFill = when (value) { - is StateMachineStatus.Added -> BackgroundFill(Color.LIGHTYELLOW, CornerRadii.EMPTY, Insets.EMPTY) - is StateMachineStatus.Removed -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY) - null -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY) - } - label.background = Background(backgroundFill) - label.text = "$value" - } - } + transactionViewStateMachineStatus.setCustomCellFactory { + val label = Label() + val backgroundFill = when (it) { + is StateMachineStatus.Added -> BackgroundFill(Color.LIGHTYELLOW, CornerRadii.EMPTY, Insets.EMPTY) + is StateMachineStatus.Removed -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY) + null -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY) } + label.background = Background(backgroundFill) + label.text = "$it" + label } transactionViewCommandTypes.setCellValueFactory { - EasyBind.map(it.value.commandTypes) { it.map { it.simpleName }.joinToString(",") } + it.value.commandTypes.map { it.map { it.simpleName }.joinToString(",") } } transactionViewTotalValueEquiv.setCellValueFactory<ViewerNode, AmountDiff<Currency>> { it.value.totalValueEquiv } transactionViewTotalValueEquiv.cellFactory = object : Formatter<AmountDiff<Currency>> { @@ -411,8 +353,8 @@ class TransactionViewer: View() { // Low level events Bindings.bindContent(lowLevelEventsTable.items, lowLevelEvents) - lowLevelEventsTimestamp.setCellValueFactory { ReadOnlyObjectWrapper(it.value.time) } - lowLevelEventsEvent.setCellValueFactory { ReadOnlyObjectWrapper(it.value) } + lowLevelEventsTimestamp.setCellValueFactory { it.value.time.lift() } + lowLevelEventsEvent.setCellValueFactory { it.value.lift() } lowLevelEventsTable.setColumnPrefWidthPolicy { tableWidthWithoutPaddingAndBorder, column -> Math.floor(tableWidthWithoutPaddingAndBorder.toDouble() / lowLevelEventsTable.columns.size).toInt() } @@ -422,3 +364,32 @@ class TransactionViewer: View() { }) } } + +/** + * We calculate the total value by subtracting relevant input states and adding relevant output states, as long as they're cash + */ +private fun calculateTotalEquiv( + identity: Party, + reportingCurrencyExchange: Pair<Currency, (Amount<Currency>) -> Amount<Currency>>, + transaction: LedgerTransaction?): AmountDiff<Currency>? { + if (transaction == null) { + return null + } + var sum = 0L + val (reportingCurrency, exchange) = reportingCurrencyExchange + val publicKey = identity.owningKey + transaction.inputs.forEach { + val contractState = it.state.data + if (contractState is Cash.State && publicKey == contractState.owner) { + sum -= exchange(contractState.amount.withoutIssuer()).quantity + } + } + transaction.outputs.forEach { + val contractState = it.data + if (contractState is Cash.State && publicKey == contractState.owner) { + sum += exchange(contractState.amount.withoutIssuer()).quantity + } + } + return AmountDiff.fromLong(sum, reportingCurrency) +} +