From a90b2ba8393eae8c13a5076681285b111f6b1e77 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Thu, 13 Apr 2017 17:31:13 +0200 Subject: [PATCH] DemoBench: UI improvements, part uno --- .../core/node/PhysicalLocationStructures.kt | 9 +- .../kotlin/net/corda/demobench/DemoBench.kt | 2 + .../corda/demobench/model/NodeController.kt | 1 + .../corda/demobench/views/DemoBenchView.kt | 2 +- .../net/corda/demobench/views/NodeTabView.kt | 100 ++++++++++++------ .../corda/demobench/views/NodeTerminalView.kt | 6 +- .../net/corda/demobench/r3-style.css | 55 ++++++++++ .../resources/net/corda/demobench/style.css | 40 ++++--- .../corda/demobench/views/DemoBenchView.fxml | 36 ++++--- .../demobench/views/NodeTerminalView.fxml | 13 ++- .../demobench/views/cordalogo-full-trans.png | Bin 0 -> 8706 bytes 11 files changed, 194 insertions(+), 70 deletions(-) create mode 100644 tools/demobench/src/main/resources/net/corda/demobench/r3-style.css create mode 100644 tools/demobench/src/main/resources/net/corda/demobench/views/cordalogo-full-trans.png diff --git a/core/src/main/kotlin/net/corda/core/node/PhysicalLocationStructures.kt b/core/src/main/kotlin/net/corda/core/node/PhysicalLocationStructures.kt index 4c24ca508c..932ca22651 100644 --- a/core/src/main/kotlin/net/corda/core/node/PhysicalLocationStructures.kt +++ b/core/src/main/kotlin/net/corda/core/node/PhysicalLocationStructures.kt @@ -48,17 +48,20 @@ data class PhysicalLocation(val coordinate: WorldCoordinate, val description: St * A simple lookup table of city names to their coordinates. Lookups are case insensitive. */ object CityDatabase { - private val cityMap = HashMap() + private val caseInsensitiveLookups = HashMap() + val cityMap = HashMap() init { javaClass.getResourceAsStream("cities.txt").bufferedReader().useLines { lines -> for (line in lines) { if (line.startsWith("#")) continue val (name, lng, lat) = line.split('\t') - cityMap[name.toLowerCase()] = PhysicalLocation(WorldCoordinate(lat.toDouble(), lng.toDouble()), name) + val location = PhysicalLocation(WorldCoordinate(lat.toDouble(), lng.toDouble()), name) + caseInsensitiveLookups[name.toLowerCase()] = location + cityMap[name] = location } } } - operator fun get(name: String) = cityMap[name.toLowerCase()] + operator fun get(name: String) = caseInsensitiveLookups[name.toLowerCase()] } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt index dcfa62ecce..4cb33ffb04 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/DemoBench.kt @@ -5,6 +5,8 @@ import net.corda.demobench.views.DemoBenchView import tornadofx.* import java.io.InputStreamReader import java.nio.charset.StandardCharsets.UTF_8 +import javax.swing.SwingUtilities +import javax.swing.UIManager /** * README! diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt index 45deefcf97..320553bde6 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/model/NodeController.kt @@ -25,6 +25,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() { private var baseDir: Path = baseDirFor(ManagementFactory.getRuntimeMXBean().startTime) private val cordaPath: Path = jvm.applicationDir.resolve("corda").resolve("corda.jar") + //private val command = arrayOf("/usr/local/bin/fish") private val command = jvm.commandFor(cordaPath).toTypedArray() private val nodes = LinkedHashMap() diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/DemoBenchView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/DemoBenchView.kt index 6f67ef87b9..24a38bc4c3 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/DemoBenchView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/DemoBenchView.kt @@ -15,7 +15,6 @@ import tornadofx.* import java.util.* class DemoBenchView : View("Corda DemoBench") { - override val root by fxml() private val profileController by inject() @@ -26,6 +25,7 @@ class DemoBenchView : View("Corda DemoBench") { private val menuSaveAs by fxid() init { + importStylesheet("/net/corda/demobench/r3-style.css") importStylesheet("/net/corda/demobench/style.css") configureShutdown() diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt index e299ae9ee0..7478c226ba 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTabView.kt @@ -1,27 +1,34 @@ package net.corda.demobench.views import javafx.application.Platform +import javafx.scene.control.ComboBox import javafx.scene.control.SelectionMode.MULTIPLE +import javafx.scene.control.SelectionMode.values import javafx.scene.input.KeyCode import javafx.scene.layout.Pane +import javafx.scene.layout.Priority import javafx.stage.FileChooser import javafx.util.converter.NumberStringConverter +import net.corda.core.node.CityDatabase import net.corda.core.utilities.DUMMY_NOTARY import net.corda.demobench.model.* import net.corda.demobench.ui.CloseableTab +import org.controlsfx.control.textfield.TextFields import tornadofx.* import java.nio.file.Path import java.text.DecimalFormat import java.util.* class NodeTabView : Fragment() { + private val cityList = CityDatabase.cityMap.keys.toList().sorted() + override val root = stackpane {} private val main by inject() private val showConfig by param(true) private companion object : Component() { - const val textWidth = 200.0 + const val textWidth = 465.0 const val numberWidth = 100.0 const val maxNameLength = 15 @@ -46,32 +53,28 @@ class NodeTabView : Fragment() { private val nodeTerminalView = find() private val nodeConfigView = stackpane { isVisible = showConfig + styleClass += "config-view" form { fieldset("Configuration") { isFillWidth = false - field("Node Name", op = { nodeNameField() }) - field("Nearest City", op = { nearestCityField() }) + field("Legal name") { nodeNameField() } + field("Nearest city") { nearestCityField() } + } + + /*fieldset("Ports") { field("P2P Port", op = { p2pPortField() }) field("RPC port", op = { rpcPortField() }) field("Web Port", op = { webPortField() }) field("Database Port", op = { databasePortField() }) - } + }*/ hbox { styleClass.addAll("node-panel") - fieldset("Services") { - styleClass.addAll("services-panel") - - listview(availableServices.observable()) { - selectionModel.selectionMode = MULTIPLE - model.item.extraServices.set(selectionModel.selectedItems) - } - } - fieldset("CorDapps") { + hboxConstraints { hGrow = Priority.ALWAYS } styleClass.addAll("cordapps-panel") listview(cordapps) { @@ -82,23 +85,41 @@ class NodeTabView : Fragment() { key.consume() } } - button("Add CorDapp") { - setOnAction { - val app = (chooser.showOpenDialog(null) ?: return@setOnAction).toPath() - if (!cordapps.contains(app)) { - cordapps.add(app) - } - } + } + + fieldset("Services") { + styleClass.addAll("services-panel") + + listview(availableServices.observable()) { + selectionModel.selectionMode = MULTIPLE + model.item.extraServices.set(selectionModel.selectedItems) } } } - button("Create Node") { - setOnAction { - if (model.validate()) { - launch() - main.enableAddNodes() - main.enableSaveProfile() + hbox { + button("Add CorDapp") { + setOnAction { + val app = (chooser.showOpenDialog(null) ?: return@setOnAction).toPath() + if (!cordapps.contains(app)) { + cordapps.add(app) + } + } + } + + // Spacer pane. + pane { + hboxConstraints { hGrow = Priority.ALWAYS } + } + + button("Start node") { + styleClass += "start-button" + setOnAction { + if (model.validate()) { + launch() + main.enableAddNodes() + main.enableSaveProfile() + } } } } @@ -117,6 +138,7 @@ class NodeTabView : Fragment() { root.add(nodeTerminalView) model.legalName.value = if (nodeController.hasNetworkMap()) "" else DUMMY_NOTARY.name + model.nearestCity.value = if (nodeController.hasNetworkMap()) "" else "London" model.p2pPort.value = nodeController.nextPort model.rpcPort.value = nodeController.nextPort model.webPort.value = nodeController.nextPort @@ -147,13 +169,29 @@ class NodeTabView : Fragment() { } } - private fun Pane.nearestCityField() = textfield(model.nearestCity) { +// private fun Pane.nearestCityField() = textfield(model.nearestCity) { +// minWidth = textWidth +// TextFields.bindAutoCompletion(this, CityDatabase.cityMap.keys) +// validator(trigger = ValidationTrigger.OnBlur) { +// if (it == null || it.trim().isEmpty()) { +// error("Nearest city is required") +// } else if (CityDatabase[it] == null) { +// error("$it is not in our database, sorry") +// } else { +// null +// } +// } +// } + + private fun Pane.nearestCityField() = combobox(model.nearestCity, cityList) { minWidth = textWidth - validator { - if (it == null) { - error("Nearest city is required") - } else if (it.trim().isEmpty()) { + isEditable = true + TextFields.bindAutoCompletion(editor, cityList) + validator(trigger = ValidationTrigger.OnBlur) { + if (it == null || it.trim().isEmpty()) { error("Nearest city is required") + } else if (CityDatabase[it] == null) { + error("$it is not in our database, sorry") } else { null } diff --git a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt index 58556076c6..bdf98cddb2 100644 --- a/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt +++ b/tools/demobench/src/main/kotlin/net/corda/demobench/views/NodeTerminalView.kt @@ -31,7 +31,6 @@ class NodeTerminalView : Fragment() { private val webServerController by inject() private val nodeName by fxid