explorer: Add input/output states and signers to tx screen

This commit is contained in:
Andras Slemmer 2016-09-02 17:18:31 +01:00
parent db9eb060b9
commit 26aed70e24
5 changed files with 332 additions and 139 deletions

View File

@ -1,7 +1,7 @@
package com.r3corda.client.model
import com.r3corda.client.fxutils.foldToObservableList
import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.core.transactions.LedgerTransaction
import com.r3corda.node.services.monitor.ServiceToClientEvent
import com.r3corda.node.services.monitor.TransactionBuildResult
import com.r3corda.node.utilities.AddOrRemove
@ -18,7 +18,7 @@ interface GatheredTransactionData {
val uuid: ObservableValue<UUID?>
val protocolName: ObservableValue<String?>
val protocolStatus: ObservableValue<ProtocolStatus?>
val transaction: ObservableValue<SignedTransaction?>
val transaction: ObservableValue<LedgerTransaction?>
val status: ObservableValue<TransactionCreateStatus?>
val lastUpdate: ObservableValue<Instant>
val allEvents: ObservableList<out ServiceToClientEvent>
@ -42,7 +42,7 @@ data class GatheredTransactionDataWritable(
override val uuid: SimpleObjectProperty<UUID?> = SimpleObjectProperty(null),
override val protocolName: SimpleObjectProperty<String?> = SimpleObjectProperty(null),
override val protocolStatus: SimpleObjectProperty<ProtocolStatus?> = SimpleObjectProperty(null),
override val transaction: SimpleObjectProperty<SignedTransaction?> = SimpleObjectProperty(null),
override val transaction: SimpleObjectProperty<LedgerTransaction?> = SimpleObjectProperty(null),
override val status: SimpleObjectProperty<TransactionCreateStatus?> = SimpleObjectProperty(null),
override val lastUpdate: SimpleObjectProperty<Instant>,
override val allEvents: ObservableList<ServiceToClientEvent> = FXCollections.observableArrayList()

View File

@ -0,0 +1,26 @@
package com.r3corda.explorer
import com.r3corda.core.contracts.Amount
enum class Positivity {
Positive,
Negative
}
val Positivity.sign: String get() = when (this) {
Positivity.Positive -> ""
Positivity.Negative -> "-"
}
data class AmountDiff<T>(
val positivity: Positivity,
val amount: Amount<T>
) {
companion object {
fun <T> fromLong(quantity: Long, token: T) =
AmountDiff(
positivity = if (quantity < 0) Positivity.Negative else Positivity.Positive,
amount = Amount(Math.abs(quantity), token)
)
}
}

View File

@ -0,0 +1,19 @@
package com.r3corda.explorer.ui
import com.r3corda.explorer.formatters.Formatter
import javafx.scene.control.ListCell
import javafx.scene.control.ListView
import javafx.util.Callback
fun <T> Formatter<T>.toListCellFactory() = Callback<ListView<T?>, ListCell<T?>> {
object : ListCell<T?>() {
override fun updateItem(value: T?, empty: Boolean) {
super.updateItem(value, empty)
text = if (value == null || empty) {
""
} else {
format(value)
}
}
}
}

View File

@ -7,12 +7,18 @@ import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.CommandData
import com.r3corda.core.contracts.withoutIssuer
import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.toStringShort
import com.r3corda.core.transactions.LedgerTransaction
import com.r3corda.explorer.AmountDiff
import com.r3corda.explorer.formatters.AmountFormatter
import com.r3corda.explorer.formatters.Formatter
import com.r3corda.explorer.formatters.NumberFormatter
import com.r3corda.explorer.model.IdentityModel
import com.r3corda.explorer.model.ReportingCurrencyModel
import com.r3corda.explorer.ui.SingleRowSelection
import com.r3corda.explorer.ui.setColumnPrefWidthPolicy
import com.r3corda.explorer.ui.singleRowSelection
import com.r3corda.explorer.ui.toTableCellFactory
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
@ -20,10 +26,8 @@ import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.geometry.Insets
import javafx.scene.control.Label
import javafx.scene.control.TableCell
import javafx.scene.control.TableColumn
import javafx.scene.control.TableView
import javafx.scene.Node
import javafx.scene.control.*
import javafx.scene.layout.Background
import javafx.scene.layout.BackgroundFill
import javafx.scene.layout.CornerRadii
@ -31,12 +35,15 @@ import javafx.scene.layout.VBox
import javafx.scene.paint.Color
import org.fxmisc.easybind.EasyBind
import tornadofx.View
import java.security.PublicKey
import java.time.Instant
import java.util.*
class TransactionViewer: View() {
override val root: VBox by fxml()
val topSplitPane: SplitPane by fxid("TopSplitPane")
// Top half (transactions table)
private val transactionViewTable: TableView<ViewerNode> by fxid("TransactionViewTable")
private val transactionViewTransactionId: TableColumn<ViewerNode, String> by fxid("TransactionViewTransactionId")
@ -44,9 +51,31 @@ class TransactionViewer: View() {
private val transactionViewTransactionStatus: TableColumn<ViewerNode, Pair<TransactionCreateStatus?, ProtocolStatus?>> by fxid("TransactionViewTransactionStatus")
private val transactionViewStatusUpdated: TableColumn<ViewerNode, Instant> by fxid("TransactionViewStatusUpdated")
private val transactionViewCommandTypes: TableColumn<ViewerNode, String> by fxid("TransactionViewCommandTypes")
private val transactionViewTotalValueEquiv: TableColumn<ViewerNode, Amount<Currency>> by fxid("TransactionViewTotalValueEquiv")
private val transactionViewTotalValueEquiv: TableColumn<ViewerNode, AmountDiff<Currency>> by fxid("TransactionViewTotalValueEquiv")
// Bottom half (details)
private val contractStatesTitledPane: TitledPane by fxid("ContractStatesTitledPane")
private val contractStatesInputStatesTable: TableView<StateNode> by fxid("ContractStatesInputStatesTable")
private val contractStatesInputStatesId: TableColumn<StateNode, String> by fxid("ContractStatesInputStatesId")
private val contractStatesInputStatesType: TableColumn<StateNode, Class<out ContractState>> by fxid("ContractStatesInputStatesType")
private val contractStatesInputStatesOwner: TableColumn<StateNode, String> by fxid("ContractStatesInputStatesOwner")
private val contractStatesInputStatesLocalCurrency: TableColumn<StateNode, Currency?> by fxid("ContractStatesInputStatesLocalCurrency")
private val contractStatesInputStatesAmount: TableColumn<StateNode, Long> by fxid("ContractStatesInputStatesAmount")
private val contractStatesInputStatesEquiv: TableColumn<StateNode, Amount<Currency>> by fxid("ContractStatesInputStatesEquiv")
private val contractStatesOutputStatesTable: TableView<StateNode> by fxid("ContractStatesOutputStatesTable")
private val contractStatesOutputStatesId: TableColumn<StateNode, String> by fxid("ContractStatesOutputStatesId")
private val contractStatesOutputStatesType: TableColumn<StateNode, Class<out ContractState>> by fxid("ContractStatesOutputStatesType")
private val contractStatesOutputStatesOwner: TableColumn<StateNode, String> by fxid("ContractStatesOutputStatesOwner")
private val contractStatesOutputStatesLocalCurrency: TableColumn<StateNode, Currency?> by fxid("ContractStatesOutputStatesLocalCurrency")
private val contractStatesOutputStatesAmount: TableColumn<StateNode, Long> by fxid("ContractStatesOutputStatesAmount")
private val contractStatesOutputStatesEquiv: TableColumn<StateNode, Amount<Currency>> by fxid("ContractStatesOutputStatesEquiv")
private val signaturesTitledPane: TitledPane by fxid("SignaturesTitledPane")
private val signaturesList: ListView<PublicKey> by fxid("SignaturesList")
private val lowLevelEventsTitledPane: TitledPane by fxid("LowLevelEventsTitledPane")
private val lowLevelEventsTable: TableView<ServiceToClientEvent> by fxid("LowLevelEventsTable")
private val lowLevelEventsTimestamp: TableColumn<ServiceToClientEvent, Instant> by fxid("LowLevelEventsTimestamp")
private val lowLevelEventsEvent: TableColumn<ServiceToClientEvent, ServiceToClientEvent> by fxid("LowLevelEventsEvent")
@ -56,17 +85,24 @@ class TransactionViewer: View() {
private val reportingExchange: ObservableValue<Pair<Currency, (Amount<Currency>) -> Amount<Currency>>>
by observableValue(ReportingCurrencyModel::reportingExchange)
private val myIdentity: ObservableValue<Party> by observableValue(IdentityModel::myIdentity)
data class ViewerNode(
val transactionId: ObservableValue<Pair<Long?, UUID?>>,
val originator: ObservableValue<String>,
val transactionStatus: ObservableValue<Pair<TransactionCreateStatus?, ProtocolStatus?>>,
val statusUpdated: ObservableValue<Instant>,
val commandTypes: ObservableValue<Collection<Class<CommandData>>>,
val viewTotalValueEquiv: ObservableValue<Amount<Currency>?>,
val transaction: ObservableValue<SignedTransaction?>,
val totalValueEquiv: ObservableValue<AmountDiff<Currency>?>,
val transaction: ObservableValue<LedgerTransaction?>,
val allEvents: ObservableList<out ServiceToClientEvent>
)
data class StateNode(
val transactionState: TransactionState<*>,
val stateRef: StateRef
)
private val viewerNodes = EasyBind.map(gatheredTransactionDataList) {
ViewerNode(
transactionId = EasyBind.combine(it.fiberId, it.uuid) { fiberId, uuid -> Pair(fiberId, uuid) },
@ -83,13 +119,13 @@ class TransactionViewer: View() {
statusUpdated = it.lastUpdate,
commandTypes = EasyBind.map(it.transaction) {
val commands = mutableSetOf<Class<CommandData>>()
it?.tx?.commands?.forEach {
it?.commands?.forEach {
commands.add(it.value.javaClass)
}
commands
},
viewTotalValueEquiv = EasyBind.combine(reportingExchange, it.transaction) { exchange, transaction ->
transaction?.let { calculateTotalEquiv(exchange.first, exchange.second, transaction) }
totalValueEquiv = EasyBind.combine(myIdentity, reportingExchange, it.transaction) { identity, exchange, transaction ->
transaction?.let { calculateTotalEquiv(setOf(identity.owningKey), exchange.first, exchange.second, transaction) }
},
transaction = it.transaction,
allEvents = it.allEvents
@ -97,16 +133,60 @@ class TransactionViewer: View() {
}
private fun calculateTotalEquiv(
relevantPublicKeys: Set<PublicKey>,
reportingCurrency: Currency,
exchange: (Amount<Currency>) -> Amount<Currency>,
transaction: SignedTransaction): Amount<Currency> {
return transaction.tx.outputs.map { it.data }.filterIsInstance<Cash.State>().fold(
initial = Amount(0, reportingCurrency),
operation = { sum, cashState -> sum + exchange(cashState.amount.withoutIssuer()) }
)
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)
}
private val selectedViewerNode = transactionViewTable.singleRowSelection()
private val selectedTransaction = EasyBind.monadic(selectedViewerNode).flatMap<LedgerTransaction?, SingleRowSelection<ViewerNode>> {
when (it) {
is SingleRowSelection.None -> ReadOnlyObjectWrapper(null)
is SingleRowSelection.Selected -> it.node.transaction
}
}
private val inputStateNodes = ChosenList<StateNode>(EasyBind.map(selectedTransaction) {
if (it == null) {
FXCollections.emptyObservableList<StateNode>()
} else {
FXCollections.observableArrayList(it.inputs.map { StateNode(it.state, it.ref) })
}
})
private val outputStateNodes = ChosenList<StateNode>(EasyBind.map(selectedTransaction) {
if (it == null) {
FXCollections.emptyObservableList<StateNode>()
} else {
FXCollections.observableArrayList(it.outputs.mapIndexed { index, transactionState ->
StateNode(transactionState, StateRef(it.id, index))
})
}
})
private val signatures = ChosenList<PublicKey>(EasyBind.map(selectedTransaction) {
if (it == null) {
FXCollections.emptyObservableList<PublicKey>()
} else {
FXCollections.observableArrayList(it.mustSign)
}
})
private val noLowLevelEvents = FXCollections.emptyObservableList<ServiceToClientEvent>()
private val lowLevelEvents = ChosenList(EasyBind.map(selectedViewerNode) {
@ -116,7 +196,78 @@ class TransactionViewer: View() {
}
})
private val allNodesShown = FXCollections.observableArrayList<Node>(
transactionViewTable,
contractStatesTitledPane,
signaturesTitledPane,
lowLevelEventsTitledPane
)
private val onlyTransactionsTableShown = FXCollections.observableArrayList<Node>(
transactionViewTable
)
private val topSplitPaneNodesShown = ChosenList<Node>(
EasyBind.map(selectedViewerNode) { selection ->
if (selection is SingleRowSelection.None<*>) {
onlyTransactionsTableShown
} else {
allNodesShown
}
})
private fun wireUpStatesTable(
states: ObservableList<StateNode>,
statesTable: TableView<StateNode>,
statesId: TableColumn<StateNode, String>,
statesType: TableColumn<StateNode, Class<out ContractState>>,
statesOwner: TableColumn<StateNode, String>,
statesLocalCurrency: TableColumn<StateNode, Currency?>,
statesAmount: TableColumn<StateNode, Long>,
statesEquiv: TableColumn<StateNode, Amount<Currency>>
) {
Bindings.bindContent(statesTable.items, states)
statesId.setCellValueFactory { ReadOnlyObjectWrapper(it.value.stateRef.toString()) }
statesType.setCellValueFactory { ReadOnlyObjectWrapper(it.value.transactionState.data.javaClass) }
statesOwner.setCellValueFactory {
val state = it.value.transactionState.data
if (state is OwnableState) {
ReadOnlyObjectWrapper(state.owner.toStringShort())
} else {
ReadOnlyObjectWrapper("???")
}
}
statesLocalCurrency.setCellValueFactory {
val state = it.value.transactionState.data
if (state is Cash.State) {
ReadOnlyObjectWrapper(state.amount.token.product)
} else {
ReadOnlyObjectWrapper(null)
}
}
statesAmount.setCellValueFactory {
val state = it.value.transactionState.data
if (state is Cash.State) {
ReadOnlyObjectWrapper(state.amount.quantity)
} else {
ReadOnlyObjectWrapper(null)
}
}
statesAmount.setCellFactory(NumberFormatter.longComma.toTableCellFactory())
statesEquiv.setCellValueFactory {
val state = it.value.transactionState.data
if (state is Cash.State) {
EasyBind.map(reportingExchange) { exchange ->
exchange.second(state.amount.withoutIssuer())
}
} else {
ReadOnlyObjectWrapper(null)
}
}
}
init {
Bindings.bindContent(topSplitPane.items, topSplitPaneNodesShown)
// Transaction table
Bindings.bindContent(transactionViewTable.items, viewerNodes)
@ -155,7 +306,7 @@ class TransactionViewer: View() {
null -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)
}
label.background = Background(backgroundFill)
label.text = if (value.first == null && value.second == null){
label.text = if (value.first == null && value.second == null) {
"???"
} else {
(value.first?.toString() ?: "") + (value.second?.let { "[${it.toString()}]" } ?: "")
@ -169,8 +320,39 @@ class TransactionViewer: View() {
transactionViewCommandTypes.setCellValueFactory {
EasyBind.map(it.value.commandTypes) { it.map { it.simpleName }.joinToString(",") }
}
transactionViewTotalValueEquiv.setCellValueFactory<ViewerNode, Amount<Currency>> { it.value.viewTotalValueEquiv }
transactionViewTotalValueEquiv.cellFactory = AmountFormatter.comma.toTableCellFactory()
transactionViewTotalValueEquiv.setCellValueFactory<ViewerNode, AmountDiff<Currency>> { it.value.totalValueEquiv }
transactionViewTotalValueEquiv.cellFactory = object : Formatter<AmountDiff<Currency>> {
override fun format(value: AmountDiff<Currency>) =
"${value.positivity.sign}${AmountFormatter.comma.format(value.amount)}"
}.toTableCellFactory()
// Contract states
wireUpStatesTable(
inputStateNodes,
contractStatesInputStatesTable,
contractStatesInputStatesId,
contractStatesInputStatesType,
contractStatesInputStatesOwner,
contractStatesInputStatesLocalCurrency,
contractStatesInputStatesAmount,
contractStatesInputStatesEquiv
)
wireUpStatesTable(
outputStateNodes,
contractStatesOutputStatesTable,
contractStatesOutputStatesId,
contractStatesOutputStatesType,
contractStatesOutputStatesOwner,
contractStatesOutputStatesLocalCurrency,
contractStatesOutputStatesAmount,
contractStatesOutputStatesEquiv
)
// Signatures
Bindings.bindContent(signaturesList.items, signatures)
signaturesList.cellFactory = object : Formatter<PublicKey> {
override fun format(value: PublicKey) = value.toStringShort()
}.toListCellFactory()
// Low level events
Bindings.bindContent(lowLevelEventsTable.items, lowLevelEvents)

View File

@ -2,8 +2,8 @@
<?import java.lang.String?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Accordion?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
@ -11,7 +11,6 @@
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
@ -40,7 +39,7 @@
</ImageView>
</children>
</StackPane>
<SplitPane dividerPositions="0.5" orientation="VERTICAL" prefHeight="562.0" prefWidth="1087.0" VBox.vgrow="ALWAYS">
<SplitPane fx:id="TopSplitPane" dividerPositions="0.5, 0.5, 0.5" orientation="VERTICAL" prefHeight="562.0" prefWidth="1087.0" VBox.vgrow="ALWAYS">
<items>
<TableView fx:id="TransactionViewTable" prefHeight="200.0" prefWidth="200.0">
<columns>
@ -55,119 +54,86 @@
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
<Accordion>
<panes>
<TitledPane text="Transaction details">
<content>
<AnchorPane>
<TitledPane fx:id="ContractStatesTitledPane" animated="false" text="Contract states">
<content>
<SplitPane dividerPositions="0.5" prefHeight="160.0" prefWidth="200.0">
<items>
<VBox prefHeight="200.0" prefWidth="100.0">
<children>
<Label layoutX="5.0" layoutY="5.0" text="Transaction ID" />
<Label layoutX="97.0" layoutY="5.0" text="IC-3902-29090-32091" AnchorPane.leftAnchor="95.0" AnchorPane.rightAnchor="15.0" />
<Label layoutX="5.0" layoutY="33.0" text="Originator" />
<Label layoutX="97.0" layoutY="33.0" prefHeight="16.0" text="C03102 HSBC GROUP PLC" AnchorPane.leftAnchor="95.0" AnchorPane.rightAnchor="15.0" />
<Label layoutX="5.0" layoutY="49.0" prefHeight="26.0" prefWidth="78.0" text="Date/time originated" wrapText="true" />
<Label layoutX="97.0" layoutY="49.0" text="2019-05-24 11:47 UTC" AnchorPane.leftAnchor="95.0" AnchorPane.rightAnchor="15.0" />
<Label layoutX="5.0" layoutY="90.0" text="Current status" />
<Label layoutX="104.0" layoutY="90.0" prefHeight="16.0" text="01800 - EXCEPTION - UNSPECIFIED NETWORK ERROR" AnchorPane.leftAnchor="95.0" AnchorPane.rightAnchor="15.0" />
<Label text="Inputs: 23">
<padding>
<Insets bottom="5.0" top="5.0" />
</padding>
</Label>
<TableView fx:id="ContractStatesInputStatesTable" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="ContractStatesInputStatesId" prefWidth="75.0" text="ID" />
<TableColumn fx:id="ContractStatesInputStatesType" prefWidth="75.0" text="Type" />
<TableColumn fx:id="ContractStatesInputStatesOwner" prefWidth="75.0" text="Owner" />
<TableColumn fx:id="ContractStatesInputStatesLocalCurrency" prefWidth="75.0" styleClass="first-column" text="Local Ccy" />
<TableColumn fx:id="ContractStatesInputStatesAmount" prefWidth="75.0" text="Amount">
<styleClass>
<String fx:value="second-column" />
<String fx:value="monetary-value" />
</styleClass>
</TableColumn>
<TableColumn fx:id="ContractStatesInputStatesEquiv" prefWidth="75.0" text="USD Equiv" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</children>
</AnchorPane>
</content></TitledPane>
<TitledPane text="Contract states">
<content>
<SplitPane dividerPositions="0.5" prefHeight="160.0" prefWidth="200.0">
<items>
<VBox prefHeight="200.0" prefWidth="100.0">
<children>
<Label text="Inputs: 23">
<padding>
<Insets bottom="5.0" top="5.0" />
</padding></Label>
<TableView prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
<columns>
<TableColumn prefWidth="75.0" text="ID" />
<TableColumn prefWidth="75.0" text="Type" />
<TableColumn prefWidth="75.0" text="Owner" />
<TableColumn prefWidth="75.0" styleClass="first-column" text="Local Ccy" />
<TableColumn prefWidth="75.0" text="Amount">
<styleClass>
<String fx:value="second-column" />
<String fx:value="monetary-value" />
</styleClass>
</TableColumn>
<TableColumn prefWidth="75.0" text="USD Equiv" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0">
<children>
<Label text="Outputs: 24">
<VBox.margin>
<Insets top="5.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" />
</padding></Label>
<TableView prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
<columns>
<TableColumn prefWidth="75.0" text="ID" />
<TableColumn prefWidth="75.0" text="Type" />
<TableColumn prefWidth="75.0" text="Owner" />
<TableColumn prefWidth="75.0" styleClass="first-column" text="Local Ccy" />
<TableColumn prefWidth="75.0" text="Amount">
<styleClass>
<String fx:value="second-column" />
<String fx:value="monetary-value" />
</styleClass>
</TableColumn>
<TableColumn prefWidth="75.0" text="USD Equiv" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</children>
</VBox>
</items>
</SplitPane>
</content>
</TitledPane>
<TitledPane prefHeight="200.0" prefWidth="200.0" text="Signatures (3/4)">
<content>
<TableView prefHeight="200.0" prefWidth="200.0">
<columns>
<TableColumn prefWidth="305.0" text="Signatory" />
<TableColumn prefWidth="221.0" text="Status" />
<TableColumn prefWidth="271.0" text="Status updated" />
</columns>
</TableView>
</content>
</TitledPane>
<TitledPane prefHeight="54.0" prefWidth="722.0" text="Attachments (3)">
<content>
<TableView prefHeight="200.0" prefWidth="200.0">
<columns>
<TableColumn prefWidth="136.0" text="File name" />
<TableColumn minWidth="8.0" prefWidth="172.0" text="Size" />
<TableColumn prefWidth="218.0" text="Date modified" />
</columns>
</TableView>
</content></TitledPane>
<TitledPane animated="false" text="Low level events">
<content>
<TableView fx:id="LowLevelEventsTable">
<columns>
<TableColumn fx:id="LowLevelEventsTimestamp" prefWidth="102.0" text="Timestamp" />
<TableColumn fx:id="LowLevelEventsEvent" prefWidth="138.0" text="Event" />
</columns>
</TableView>
</content>
</TitledPane>
</panes>
</Accordion>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0">
<children>
<Label text="Outputs: 24">
<VBox.margin>
<Insets top="5.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" />
</padding>
</Label>
<TableView fx:id="ContractStatesOutputStatesTable" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="ContractStatesOutputStatesId" prefWidth="75.0" text="ID" />
<TableColumn fx:id="ContractStatesOutputStatesType" prefWidth="75.0" text="Type" />
<TableColumn fx:id="ContractStatesOutputStatesOwner" prefWidth="75.0" text="Owner" />
<TableColumn fx:id="ContractStatesOutputStatesLocalCurrency" prefWidth="75.0" styleClass="first-column" text="Local Ccy" />
<TableColumn fx:id="ContractStatesOutputStatesAmount" prefWidth="75.0" text="Amount">
<styleClass>
<String fx:value="second-column" />
<String fx:value="monetary-value" />
</styleClass>
</TableColumn>
<TableColumn fx:id="ContractStatesOutputStatesEquiv" prefWidth="75.0" text="USD Equiv" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</children>
</VBox>
</items>
</SplitPane>
</content>
</TitledPane>
<TitledPane fx:id="SignaturesTitledPane" animated="false" text="Required signatures">
<content>
<ListView fx:id="SignaturesList" />
</content>
</TitledPane>
<TitledPane fx:id="LowLevelEventsTitledPane" animated="false" text="Low level events">
<content>
<TableView fx:id="LowLevelEventsTable">
<columns>
<TableColumn fx:id="LowLevelEventsTimestamp" prefWidth="102.0" text="Timestamp" />
<TableColumn fx:id="LowLevelEventsEvent" prefWidth="138.0" text="Event" />
</columns>
</TableView>
</content>
</TitledPane>
</items>
</SplitPane>
<Label text="133 matching transaction(s)" />