DemoBench: better autocomplete and flags for city list
@ -59,7 +59,7 @@ object CityDatabase {
|
||||
if (line.startsWith("#")) continue
|
||||
val (name, lng, lat) = line.split('\t')
|
||||
val matchResult = matcher.matchEntire(name) ?: throw Exception("Could not parse line: $line")
|
||||
val (city, country) = matchResult!!.destructured
|
||||
val (city, country) = matchResult.destructured
|
||||
val location = PhysicalLocation(WorldCoordinate(lat.toDouble(), lng.toDouble()), city, country)
|
||||
caseInsensitiveLookups[city.toLowerCase()] = location
|
||||
cityMap[city] = location
|
||||
|
@ -484,7 +484,6 @@ San Miguel De Tucuman (AR) -65.22 -26.82
|
||||
Changwat Phra Nakhon Si Ayutthaya (TH) 100.53 14.33
|
||||
Selam (IN) 78.17 11.65
|
||||
Taroudannt (MA) -8.87 30.47
|
||||
Reunion (RE) 55.52 -20.87
|
||||
Tiruchchirappalli (IN) 78.68 10.8
|
||||
Hims (SY) 36.72 34.72
|
||||
Hohhot (CN) 111.65 40.8
|
||||
|
@ -1,5 +1,5 @@
|
||||
buildscript {
|
||||
ext.tornadofx_version = '1.6.2'
|
||||
ext.tornadofx_version = '1.7.1'
|
||||
ext.jna_version = '4.1.0'
|
||||
ext.purejavacomm_version = '0.0.17'
|
||||
ext.controlsfx_version = '8.40.12'
|
||||
@ -33,10 +33,15 @@ repositories {
|
||||
dirs 'libs'
|
||||
}
|
||||
maven {
|
||||
jcenter()
|
||||
url 'http://www.sparetimelabs.com/maven2'
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget= "1.8"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's.
|
||||
compile "no.tornado:tornadofx:$tornadofx_version"
|
||||
@ -57,6 +62,9 @@ dependencies {
|
||||
compile "org.apache.logging.log4j:log4j-core:$log4j_version"
|
||||
compile "com.typesafe:config:$typesafe_config_version"
|
||||
|
||||
// FontAwesomeFX: icons in the form of a font.
|
||||
compile "de.jensd:fontawesomefx-fontawesome:4.7.0-5"
|
||||
|
||||
// These libraries don't exist in any Maven repository I can find.
|
||||
// See: https://github.com/JetBrains/jediterm
|
||||
//
|
||||
|
@ -55,7 +55,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
|
||||
nodeData.legalName.value.trim(),
|
||||
nodeData.p2pPort.value,
|
||||
nodeData.rpcPort.value,
|
||||
nodeData.nearestCity.value.trim(),
|
||||
nodeData.nearestCity.value.description.trim(),
|
||||
nodeData.webPort.value,
|
||||
nodeData.h2Port.value,
|
||||
nodeData.extraServices.value
|
||||
|
@ -2,13 +2,15 @@ package net.corda.demobench.model
|
||||
|
||||
import javafx.beans.property.SimpleIntegerProperty
|
||||
import javafx.beans.property.SimpleListProperty
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import net.corda.core.node.CityDatabase
|
||||
import tornadofx.*
|
||||
|
||||
class NodeData {
|
||||
|
||||
val legalName = SimpleStringProperty("")
|
||||
val nearestCity = SimpleStringProperty("London")
|
||||
val nearestCity = SimpleObjectProperty(CityDatabase["London"]!!)
|
||||
val p2pPort = SimpleIntegerProperty()
|
||||
val rpcPort = SimpleIntegerProperty()
|
||||
val webPort = SimpleIntegerProperty()
|
||||
|
@ -1,27 +1,30 @@
|
||||
package net.corda.demobench.views
|
||||
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
|
||||
import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory
|
||||
import javafx.application.Platform
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.control.ComboBox
|
||||
import javafx.scene.control.SelectionMode.MULTIPLE
|
||||
import javafx.scene.control.SelectionMode.values
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.image.ImageView
|
||||
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 javafx.util.StringConverter
|
||||
import net.corda.core.node.CityDatabase
|
||||
import net.corda.core.node.PhysicalLocation
|
||||
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<DemoBenchView>()
|
||||
@ -29,11 +32,9 @@ class NodeTabView : Fragment() {
|
||||
|
||||
private companion object : Component() {
|
||||
const val textWidth = 465.0
|
||||
const val numberWidth = 100.0
|
||||
const val maxNameLength = 15
|
||||
|
||||
val integerFormat = DecimalFormat()
|
||||
val notNumber = "[^\\d]".toRegex()
|
||||
|
||||
val jvm by inject<JVMConfig>()
|
||||
|
||||
@ -63,13 +64,6 @@ class NodeTabView : Fragment() {
|
||||
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")
|
||||
|
||||
@ -105,6 +99,8 @@ class NodeTabView : Fragment() {
|
||||
cordapps.add(app)
|
||||
}
|
||||
}
|
||||
|
||||
FontAwesomeIconFactory.get().setIcon(this, FontAwesomeIcon.PLUS)
|
||||
}
|
||||
|
||||
// Spacer pane.
|
||||
@ -121,6 +117,7 @@ class NodeTabView : Fragment() {
|
||||
main.enableSaveProfile()
|
||||
}
|
||||
}
|
||||
graphic = FontAwesomeIconView(FontAwesomeIcon.PLAY_CIRCLE).apply { style += "-fx-fill: white" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -133,12 +130,13 @@ class NodeTabView : Fragment() {
|
||||
nodeTab.setOnCloseRequest {
|
||||
nodeTerminalView.destroy()
|
||||
}
|
||||
nodeTab.graphic = FontAwesomeIconView(FontAwesomeIcon.BANK)
|
||||
|
||||
root.add(nodeConfigView)
|
||||
root.add(nodeTerminalView)
|
||||
|
||||
model.legalName.value = if (nodeController.hasNetworkMap()) "" else DUMMY_NOTARY.name
|
||||
model.nearestCity.value = if (nodeController.hasNetworkMap()) "" else "London"
|
||||
model.nearestCity.value = if (nodeController.hasNetworkMap()) null else CityDatabase["London"]!!
|
||||
model.p2pPort.value = nodeController.nextPort
|
||||
model.rpcPort.value = nodeController.nextPort
|
||||
model.webPort.value = nodeController.nextPort
|
||||
@ -147,6 +145,8 @@ class NodeTabView : Fragment() {
|
||||
chooser.title = "CorDapps"
|
||||
chooser.initialDirectory = jvm.dataHome.toFile()
|
||||
chooser.extensionFilters.add(FileChooser.ExtensionFilter("CorDapps (*.jar)", "*.jar", "*.JAR"))
|
||||
|
||||
model.validate(focusFirstError = true)
|
||||
}
|
||||
|
||||
private fun Pane.nodeNameField() = textfield(model.legalName) {
|
||||
@ -169,128 +169,38 @@ class NodeTabView : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
}
|
||||
}
|
||||
private val flags = runAsync {
|
||||
CityDatabase.cityMap.values.map { it.countryCode }.toSet().map { it to Image(resources["/net/corda/demobench/flags/$it.png"]) }.toMap()
|
||||
}
|
||||
|
||||
private fun Pane.p2pPortField() = textfield(model.p2pPort, NumberStringConverter(integerFormat)) {
|
||||
minWidth = numberWidth
|
||||
validator {
|
||||
if ((it == null) || it.isEmpty()) {
|
||||
error("Port number required")
|
||||
} else if (it.contains(notNumber)) {
|
||||
error("Invalid port number")
|
||||
} else {
|
||||
val port = it.toInt()
|
||||
if (!nodeController.isPortAvailable(port)) {
|
||||
error("Port $it is unavailable")
|
||||
} else if (port == model.rpcPort.value) {
|
||||
error("Clashes with RPC port")
|
||||
} else if (port == model.webPort.value) {
|
||||
error("Clashes with web port")
|
||||
} else if (port == model.h2Port.value) {
|
||||
error("Clashes with database port")
|
||||
} else {
|
||||
null
|
||||
private fun Pane.nearestCityField(): ComboBox<PhysicalLocation> {
|
||||
return combobox(model.nearestCity, CityDatabase.cityMap.values.toList().sortedBy { it.description }) {
|
||||
minWidth = textWidth
|
||||
styleClass += "city-picker"
|
||||
cellFormat {
|
||||
graphic = hbox(spacing = 10) {
|
||||
imageview {
|
||||
image = flags.get()[it.countryCode]
|
||||
}
|
||||
label(it.description)
|
||||
alignment = Pos.CENTER_LEFT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Pane.rpcPortField() = textfield(model.rpcPort, NumberStringConverter(integerFormat)) {
|
||||
minWidth = 100.0
|
||||
validator {
|
||||
if ((it == null) || it.isEmpty()) {
|
||||
error("Port number required")
|
||||
} else if (it.contains(notNumber)) {
|
||||
error("Invalid port number")
|
||||
} else {
|
||||
val port = it.toInt()
|
||||
if (!nodeController.isPortAvailable(port)) {
|
||||
error("Port $it is unavailable")
|
||||
} else if (port == model.p2pPort.value) {
|
||||
error("Clashes with P2P port")
|
||||
} else if (port == model.webPort.value) {
|
||||
error("Clashes with web port")
|
||||
} else if (port == model.h2Port.value) {
|
||||
error("Clashes with database port")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
validator {
|
||||
if (it == null) error("Please select a city") else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Pane.webPortField() = textfield(model.webPort, NumberStringConverter(integerFormat)) {
|
||||
minWidth = numberWidth
|
||||
validator {
|
||||
if ((it == null) || it.isEmpty()) {
|
||||
error("Port number required")
|
||||
} else if (it.contains(notNumber)) {
|
||||
error("Invalid port number")
|
||||
} else {
|
||||
val port = it.toInt()
|
||||
if (!nodeController.isPortAvailable(port)) {
|
||||
error("Port $it is unavailable")
|
||||
} else if (port == model.p2pPort.value) {
|
||||
error("Clashes with P2P port")
|
||||
} else if (port == model.rpcPort.value) {
|
||||
error("Clashes with RPC port")
|
||||
} else if (port == model.h2Port.value) {
|
||||
error("Clashes with database port")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
converter = object : StringConverter<PhysicalLocation>() {
|
||||
override fun toString(loc: PhysicalLocation?) = loc?.description ?: ""
|
||||
override fun fromString(string: String): PhysicalLocation? = CityDatabase[string]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Pane.databasePortField() = textfield(model.h2Port, NumberStringConverter(integerFormat)) {
|
||||
minWidth = numberWidth
|
||||
validator {
|
||||
if ((it == null) || it.isEmpty()) {
|
||||
error("Port number required")
|
||||
} else if (it.contains(notNumber)) {
|
||||
error("Invalid port number")
|
||||
} else {
|
||||
val port = it.toInt()
|
||||
if (!nodeController.isPortAvailable(port)) {
|
||||
error("Port $it is unavailable")
|
||||
} else if (port == model.p2pPort.value) {
|
||||
error("Clashes with P2P port")
|
||||
} else if (port == model.rpcPort.value) {
|
||||
error("Clashes with RPC port")
|
||||
} else if (port == model.webPort.value) {
|
||||
error("Clashes with web port")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
value = CityDatabase["London"]
|
||||
|
||||
// TODO: Find a way to make the popup the same width as when not auto-completing.
|
||||
isEditable = true
|
||||
makeAutocompletable()
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,6 +212,7 @@ class NodeTabView : Fragment() {
|
||||
val config = nodeController.validate(model.item)
|
||||
if (config != null) {
|
||||
nodeConfigView.isVisible = false
|
||||
nodeTab.graphic = ImageView(flags.get()[model.nearestCity.value.countryCode]).apply { fitWidth = 24.0; isPreserveRatio = true }
|
||||
config.install(cordapps)
|
||||
launchNode(config)
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 824 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 850 B |
After Width: | Height: | Size: 935 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 961 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 826 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1017 B |
After Width: | Height: | Size: 850 B |
After Width: | Height: | Size: 991 B |
After Width: | Height: | Size: 772 B |
After Width: | Height: | Size: 994 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 913 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 893 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 853 B |
After Width: | Height: | Size: 873 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 888 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 862 B |
After Width: | Height: | Size: 835 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 982 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1008 B |
After Width: | Height: | Size: 984 B |
After Width: | Height: | Size: 761 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 884 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 979 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 746 B |
After Width: | Height: | Size: 849 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 778 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 902 B |
After Width: | Height: | Size: 903 B |
After Width: | Height: | Size: 906 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 837 B |
After Width: | Height: | Size: 918 B |
After Width: | Height: | Size: 960 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1008 B |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 940 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 942 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 982 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 993 B |