diff --git a/README.md b/README.md
index b2dbcfd6b..5114ed2eb 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@ You also need a full rebuild of your code after a version upgrade. If you run in
```bash
mvn archetype:generate -DarchetypeGroupId=no.tornado \
-DarchetypeArtifactId=tornadofx-quickstart-archetype \
- -DarchetypeVersion=1.7.17
+ -DarchetypeVersion=1.7.20
```
### Add TornadoFX to your project
@@ -79,14 +79,14 @@ mvn archetype:generate -DarchetypeGroupId=no.tornado \
no.tornadotornadofx
- 1.7.17
+ 1.7.20
```
### Gradle
```groovy
-implementation 'no.tornado:tornadofx:1.7.17'
+implementation 'no.tornado:tornadofx:1.7.20'
```
### Snapshots are published to Sonatype
diff --git a/pom.xml b/pom.xml
index 900d2b607..876777e8d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -311,7 +311,7 @@
13
- 1.3.61
+ 1.3.720.10.01.1.2
diff --git a/src/main/java/tornadofx/Collections.kt b/src/main/java/tornadofx/Collections.kt
index 13bca1bbb..2543dd671 100644
--- a/src/main/java/tornadofx/Collections.kt
+++ b/src/main/java/tornadofx/Collections.kt
@@ -5,6 +5,8 @@ package tornadofx
import javafx.beans.Observable
import javafx.beans.WeakListener
import javafx.collections.*
+import javafx.collections.transformation.FilteredList
+import javafx.collections.transformation.SortedList
import tornadofx.FX.IgnoreParentBuilder.No
import tornadofx.FX.IgnoreParentBuilder.Once
import java.lang.ref.WeakReference
@@ -49,7 +51,7 @@ fun observableListOf(collection: Collection): ObservableList = FXColle
/**
* Returns an empty new [ObservableList] with the given [extractor]. This list reports element updates.
*/
-fun observableListOf(extractor: (T)->Array): ObservableList = FXCollections.observableArrayList(extractor)
+fun observableListOf(extractor: (T) -> Array): ObservableList = FXCollections.observableArrayList(extractor)
/**
* Returns an empty new [ObservableSet]
@@ -372,15 +374,34 @@ fun MutableList.bind(sourceList: Observable
}
}
val listener = ListConversionListener(this, ignoringParentConverter)
- (this as? ObservableList)?.setAll(sourceList.map(ignoringParentConverter)) ?: run {
+ (this as? ObservableList)?.setAll(sourceList.map(ignoringParentConverter)) ?: run {
clear()
addAll(sourceList.map(ignoringParentConverter))
}
sourceList.removeListener(listener)
sourceList.addListener(listener)
+
+ when (sourceList) {
+ is FilteredList -> sourceList.source.addListener(invalidateList(sourceList))
+ is SortedList -> sourceList.source.addListener(invalidateList(sourceList))
+ is SortedFilteredList -> sourceList.sortedItems.source.addListener(invalidateList(sourceList))
+ }
+
return listener
}
+fun invalidateList(sourceList: ObservableList): ListChangeListener = object : ListChangeListener, WeakListener {
+ private val ref: WeakReference> = WeakReference(sourceList)
+
+ override fun onChanged(change: ListChangeListener.Change) {
+ val list = ref.get()
+ if (list == null) change.list.removeListener(this)
+ else while (change.next()) list.invalidate()
+ }
+
+ override fun wasGarbageCollected() = ref.get() == null
+}
+
/**
* Bind this list to the given observable list by converting them into the correct type via the given converter.
* Changes to the observable list are synced.
@@ -425,13 +446,13 @@ fun MutableList.bind(
val listener = MapConversionListener(this, ignoringParentConverter)
if (this is ObservableList<*>) {
sourceMap.forEach { source ->
- val converted = ignoringParentConverter(source.key,source.value)
- listener.sourceToTarget[source] = converted
+ val converted = ignoringParentConverter(source.key, source.value)
+ listener.sourceToTarget[source.key] = converted
}
(this as ObservableList).setAll(listener.sourceToTarget.values)
} else {
clear()
- addAll(sourceMap.map{ignoringParentConverter(it.key, it.value) })
+ addAll(sourceMap.map { ignoringParentConverter(it.key, it.value) })
}
sourceMap.removeListener(listener)
sourceMap.addListener(listener)
@@ -494,19 +515,23 @@ class MapConversionListener(
targetList: MutableList,
val converter: (SourceTypeKey, SourceTypeValue) -> TargetType
) : MapChangeListener, WeakListener {
-
internal val targetRef: WeakReference> = WeakReference(targetList)
- internal val sourceToTarget = HashMap, TargetType>()
+ internal val sourceToTarget = HashMap()
+
override fun onChanged(change: MapChangeListener.Change) {
val list = targetRef.get()
if (list == null) {
change.map.removeListener(this)
+ sourceToTarget.clear()
} else {
if (change.wasRemoved()) {
- list.remove(converter(change.key, change.valueRemoved))
+ list.remove(sourceToTarget[change.key])
+ sourceToTarget.remove(change.key)
}
if (change.wasAdded()) {
- list.add(converter(change.key, change.valueAdded))
+ val converted = converter(change.key, change.valueAdded)
+ sourceToTarget[change.key] = converted
+ list.add(converted)
}
}
}
@@ -576,8 +601,16 @@ class SetConversionListener(targetList: MutableList ObservableList.invalidate() {
- if (isNotEmpty()) this[0] = this[0]
+ // bug compiler: Error:(606, 31) Kotlin: Cannot access 'predicate': it is private in 'FilteredList'
+ @Suppress("UsePropertyAccessSyntax")
+ if (isNotEmpty()) when (this) {
+ is FilteredList -> setPredicate(getPredicate())
+ is SortedList -> setComparator(getComparator())
+ // invalidate FilteredList ?
+ is SortedFilteredList -> sortedItems.comparator = sortedItems.comparator
+ else -> this[0] = this[0]
+ }
}
@Deprecated("Use `observableListOf()` instead.", ReplaceWith("observableListOf(entries)", "tornadofx.observableListOf"))
-fun observableList(vararg entries: T) : ObservableList = FXCollections.observableArrayList(entries.toList())
+fun observableList(vararg entries: T): ObservableList = FXCollections.observableArrayList(entries.toList())
diff --git a/src/main/java/tornadofx/Component.kt b/src/main/java/tornadofx/Component.kt
index bc19b1f2a..5466dc7a4 100644
--- a/src/main/java/tornadofx/Component.kt
+++ b/src/main/java/tornadofx/Component.kt
@@ -6,6 +6,7 @@ import javafx.application.HostServices
import javafx.beans.binding.BooleanExpression
import javafx.beans.property.*
import javafx.beans.value.ChangeListener
+import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.concurrent.Task
import javafx.event.EventDispatchChain
@@ -1318,6 +1319,100 @@ fun U.whenUndockedOnce(listener: (U) -> Unit) {
whenUndocked(wrapped)
}
+/**
+ * This extension function will automatically bind to the managedProperty of the given node
+ * and will make sure that it is managed, if the given [expr] returning an observable boolean value equals true.
+ *
+ * @see https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#managedProperty
+ */
+fun T.managedWhen(expr: () -> ObservableValue): T = managedWhen(expr())
+
+/**
+ * This extension function will automatically bind to the managedProperty of the given node
+ * and will make sure that it is managed, if the given [predicate] an observable boolean value equals true.
+ *
+ * @see https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#managedProperty)
+ */
+fun T.managedWhen(predicate: ObservableValue): T = apply {
+ root.managedWhen(predicate)
+}
+
+/**
+ * This extension function will automatically bind to the visibleProperty of the given node
+ * and will make sure that it is visible, if the given [predicate] an observable boolean value equals true.
+ *
+ * @see https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#visibleProperty
+ */
+fun T.visibleWhen(predicate: ObservableValue): T = apply {
+ root.visibleWhen(predicate)
+}
+
+/**
+ * This extension function will automatically bind to the visibleProperty of the given node
+ * and will make sure that it is visible, if the given [expr] returning an observable boolean value equals true.
+ *
+ * @see https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#visibleProperty
+ */
+fun T.visibleWhen(expr: () -> ObservableValue): T = visibleWhen(expr())
+
+/**
+ * This extension function will make sure to hide the given node,
+ * if the given [expr] returning an observable boolean value equals true.
+ */
+fun T.hiddenWhen(expr: () -> ObservableValue): T = hiddenWhen(expr())
+
+/**
+ * This extension function will make sure to hide the given node,
+ * if the given [predicate] an observable boolean value equals true.
+ */
+fun T.hiddenWhen(predicate: ObservableValue) = apply {
+ root.hiddenWhen(predicate)
+}
+
+/**
+ * This extension function will automatically bind to the disableProperty of the given node
+ * and will disable it, if the given [expr] returning an observable boolean value equals true.
+ *
+ * @see https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#disable
+ */
+fun T.disableWhen(expr: () -> ObservableValue): T = disableWhen(expr())
+
+/**
+ * This extension function will automatically bind to the disableProperty of the given node
+ * and will disable it, if the given [predicate] observable boolean value equals true.
+ *
+ * @see https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#disableProperty
+ */
+fun T.disableWhen(predicate: ObservableValue) = apply {
+ root.disableWhen(predicate)
+}
+
+/**
+ * This extension function will make sure that the given UI component is enabled when ever,
+ * the given [expr] returning an observable boolean value equals true.
+ */
+fun T.enableWhen(expr: () -> ObservableValue): T = enableWhen(expr())
+
+/**
+ * This extension function will make sure that the given UI component is enabled when ever,
+ * the given [predicate] observable boolean value equals true.
+ */
+fun T.enableWhen(predicate: ObservableValue) = apply {
+ root.enableWhen(predicate)
+}
+
+/**
+ * This extension function will make sure that the given UI component will only be visible in the scene graph,
+ * if the given [expr] returning an observable boolean value equals true.
+ */
+fun T.removeWhen(predicate: ObservableValue) = root.removeWhen(predicate)
+
+/**
+ * This extension function will make sure that the given UI component will only be visible in the scene graph,
+ * if the given [predicate] observable boolean value equals true.
+ */
+fun T.removeWhen(expr: () -> ObservableValue): T = apply { root.removeWhen(expr()) }
+
abstract class Fragment @JvmOverloads constructor(title: String? = null, icon: Node? = null) : UIComponent(title, icon)
abstract class View @JvmOverloads constructor(title: String? = null, icon: Node? = null) : UIComponent(title, icon), ScopedInstance
diff --git a/src/main/java/tornadofx/FX.kt b/src/main/java/tornadofx/FX.kt
index fc8db1b5a..a74583e18 100644
--- a/src/main/java/tornadofx/FX.kt
+++ b/src/main/java/tornadofx/FX.kt
@@ -679,12 +679,9 @@ fun EventTarget.getChildList(): MutableList? = when (this) {
@Suppress("UNCHECKED_CAST", "PLATFORM_CLASS_MAPPED_TO_KOTLIN")
private fun Parent.getChildrenReflectively(): MutableList? {
- val getter = this.javaClass.findMethodByName("getChildren")
- if (getter != null && java.util.List::class.java.isAssignableFrom(getter.returnType)) {
- getter.isAccessible = true
- return getter.invoke(this) as MutableList
- }
- return null
+ return javaClass.findMethodByName("getChildren")
+ ?.takeIf { java.util.List::class.java.isAssignableFrom(it.returnType) && canOrSetAccessMethod(it) }
+ ?.invoke(this) as MutableList
}
var Window.aboutToBeShown: Boolean
diff --git a/src/main/java/tornadofx/ItemControls.kt b/src/main/java/tornadofx/ItemControls.kt
index d798342ea..74619c326 100644
--- a/src/main/java/tornadofx/ItemControls.kt
+++ b/src/main/java/tornadofx/ItemControls.kt
@@ -860,7 +860,7 @@ fun TableView.selectOnDrag() {
var startColumn = columns.first()
// Record start position and clear selection unless Control is down
- addEventFilter(MouseEvent.MOUSE_PRESSED) {
+ addEventHandler(MouseEvent.MOUSE_PRESSED) {
startRow = 0
(it.pickResult.intersectedNode as? TableCell<*, *>)?.apply {
@@ -876,7 +876,7 @@ fun TableView.selectOnDrag() {
}
// Select items while dragging
- addEventFilter(MouseEvent.MOUSE_DRAGGED) {
+ addEventHandler(MouseEvent.MOUSE_DRAGGED) {
(it.pickResult.intersectedNode as? TableCell<*, *>)?.apply {
if (items.size > index) {
if (selectionModel.isCellSelectionEnabled) {
@@ -936,12 +936,12 @@ class TableViewEditModel(val tableView: TableView) {
// Add columns and track changes to columns
tableView.columns.forEach(::addEventHandlerForColumn)
- tableView.columns.addListener({ change: ListChangeListener.Change> ->
+ tableView.columns.addListener { change: ListChangeListener.Change> ->
while (change.next()) {
if (change.wasAdded())
change.addedSubList.forEach(::addEventHandlerForColumn)
}
- })
+ }
// Remove dirty state for items removed from the TableView
val listenForRemovals = ListChangeListener {
diff --git a/src/main/java/tornadofx/Json.kt b/src/main/java/tornadofx/Json.kt
index e4b9e5798..562666c1d 100644
--- a/src/main/java/tornadofx/Json.kt
+++ b/src/main/java/tornadofx/Json.kt
@@ -126,7 +126,7 @@ fun JsonObject.getDouble(vararg key: String): Double = double(*key)!!
fun JsonObject.jsonNumber(vararg key: String): JsonNumber? = firstNonNull(*key) { getJsonNumber(it) }
fun JsonObject.getJsonNumber(vararg key: String): JsonNumber = jsonNumber(*key)!!
-fun JsonObject.float(vararg key: String): Float? = firstNonNull(*key) { getFloat(it) }
+fun JsonObject.float(vararg key: String): Float? = firstNonNull(*key) { double(it) }?.toFloat()
fun JsonObject.getFloat(vararg key: String): Float = float(*key)!!
fun JsonObject.bigdecimal(vararg key: String): BigDecimal? = jsonNumber(*key)?.bigDecimalValue()
diff --git a/src/main/java/tornadofx/Lib.kt b/src/main/java/tornadofx/Lib.kt
index b9ca99bb3..08c02e618 100644
--- a/src/main/java/tornadofx/Lib.kt
+++ b/src/main/java/tornadofx/Lib.kt
@@ -15,6 +15,7 @@ import javafx.scene.input.DataFormat
import javafx.scene.media.Media
import javafx.scene.media.MediaPlayer
import java.io.File
+import java.lang.reflect.Method
import java.util.function.Predicate
/**
@@ -438,4 +439,13 @@ fun > Sequence.mapEachTo(destination: C, ac
*/
fun > Array.mapEachTo(destination: C, action: T.() -> R) = mapTo(destination, action)
-fun Media.play() = MediaPlayer(this).play()
\ No newline at end of file
+fun Media.play() = MediaPlayer(this).play()
+
+
+/**
+ * Checks if a method is available for this, or tries to set access
+ *
+ * @return true if access for this this is or has been set
+ */
+// Java 9+
+internal fun Any.canOrSetAccessMethod(method: Method): Boolean = method.canAccess(this) || method.trySetAccessible()
\ No newline at end of file
diff --git a/src/main/java/tornadofx/Nodes.kt b/src/main/java/tornadofx/Nodes.kt
index 868023ee0..a85a1a29a 100644
--- a/src/main/java/tornadofx/Nodes.kt
+++ b/src/main/java/tornadofx/Nodes.kt
@@ -925,10 +925,11 @@ fun Node.replaceWith(
return true
} else if (parent is Pane) {
val parent = parent as Pane
- val attach = if (parent is BorderPane) {
+ val attach: (Node) -> Unit = if (parent is BorderPane) {
when (this) {
parent.top -> {
- { it: Node -> parent.top = it }
+ @Suppress("RedundantLambdaArrow") // bug compiler
+ { it: Node -> parent.top = it }
}
parent.right -> {
{ parent.right = it }
@@ -943,7 +944,7 @@ fun Node.replaceWith(
{ parent.center = it }
}
else -> {
- { throw IllegalStateException("Child of BorderPane not found in BorderPane") }
+ { it: Node -> throw IllegalStateException("Child of BorderPane not found in BorderPane") }
}
}
} else {
diff --git a/src/main/java/tornadofx/skin/tablerow/DirtyDecoratingTableRowSkin.java b/src/main/java/tornadofx/skin/tablerow/DirtyDecoratingTableRowSkin.java
index 426e4f647..9031fa9b8 100644
--- a/src/main/java/tornadofx/skin/tablerow/DirtyDecoratingTableRowSkin.java
+++ b/src/main/java/tornadofx/skin/tablerow/DirtyDecoratingTableRowSkin.java
@@ -9,6 +9,8 @@
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import tornadofx.TableViewEditModel;
+import java.util.ArrayList;
+import java.util.List;
// This needs to be a Java class because of a Kotlin Bug:
// https://youtrack.jetbrains.com/issue/KT-12255
@@ -23,8 +25,8 @@ public DirtyDecoratingTableRowSkin(TableRow tableRow, TableViewEditModel e
this.editModel = editModel;
}
- private Polygon getPolygon(TableCell cell) {
- ObservableMap properties = cell.getProperties();
+ private Polygon getPolygon(TableCell, ?> cell) {
+ ObservableMap