From 7c6a44d5b227b9bddc193534204b5e45a5941919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Gonz=C3=A1lez=20Abril?= Date: Sun, 28 Oct 2018 23:36:49 +0100 Subject: [PATCH 1/5] Refactored Controls.kt Applied the Kotlin Coding Conventions (https://kotlinlang.org/docs/reference/coding-conventions.html) Sorted and grouped functions based on functionality. Added some function variations with `ObservableValue` instead of `X`. (Being X a given type) Added some parameters and default values to be consistent. Renamed some parameters to be consistent. Changed signature of some functions to be more generic, ie: `Property` changed to `ObservableValue`. (Should not break existing code) Removed some redundant functions. Made some `value` parameters nullable to be consistent with the existing code. Added support for more classes in `EventTarget.properties`. Changed `TextInputControl.stripNonNumeric` to accept chars instead of strings. Changed some properties keys, should not break existing code. --- src/main/java/tornadofx/Controls.kt | 643 ++++++++++++++++++---------- src/main/java/tornadofx/Menu.kt | 6 +- src/main/java/tornadofx/Nodes.kt | 2 +- 3 files changed, 413 insertions(+), 238 deletions(-) diff --git a/src/main/java/tornadofx/Controls.kt b/src/main/java/tornadofx/Controls.kt index a3c30a7e7..50ac98131 100644 --- a/src/main/java/tornadofx/Controls.kt +++ b/src/main/java/tornadofx/Controls.kt @@ -1,5 +1,3 @@ -@file:Suppress("UNCHECKED_CAST") - package tornadofx import javafx.beans.property.ObjectProperty @@ -10,6 +8,7 @@ import javafx.collections.ObservableMap import javafx.event.EventTarget import javafx.geometry.Orientation import javafx.scene.Node +import javafx.scene.Scene import javafx.scene.control.* import javafx.scene.image.Image import javafx.scene.image.ImageView @@ -19,41 +18,35 @@ import javafx.scene.text.Text import javafx.scene.text.TextFlow import javafx.scene.web.HTMLEditor import javafx.scene.web.WebView +import javafx.stage.Window import javafx.util.StringConverter import java.time.LocalDate -fun EventTarget.webview(op: WebView.() -> Unit = {}) = WebView().attachTo(this, op) -enum class ColorPickerMode { Button, MenuButton, SplitMenuButton } +internal const val VALUE_PROPERTY = "tornadofx.value" +internal const val SELECTED_VALUE_PROPERTY = "tornadofx.selectedValue" +internal const val TOGGLE_GROUP_PROPERTY = "tornadofx.toggleGroup" +internal const val TOGGLE_GROUP_VALUE_PROPERTY = "tornadofx.toggleGroupValue" -fun EventTarget.colorpicker( - color: Color? = null, - mode: ColorPickerMode = ColorPickerMode.Button, - op: ColorPicker.() -> Unit = {} -) = ColorPicker().attachTo(this, op) { - if (mode == ColorPickerMode.MenuButton) it.addClass(ColorPicker.STYLE_CLASS_BUTTON) - else if (mode == ColorPickerMode.SplitMenuButton) it.addClass(ColorPicker.STYLE_CLASS_SPLIT_BUTTON) - if (color != null) it.value = color -} - -fun EventTarget.textflow(op: TextFlow.() -> Unit = {}) = TextFlow().attachTo(this, op) - -fun EventTarget.text(op: Text.() -> Unit = {}) = Text().attachTo(this, op) internal val EventTarget.properties: ObservableMap get() = when (this) { - is Node -> properties + is Scene -> properties + is Window -> properties is Tab -> properties + is Node -> properties is MenuItem -> properties + is Toggle -> properties + is ToggleGroup -> properties + is TableColumnBase<*, *> -> this.properties // Explicit this is required due to weird SmartCast else -> throw IllegalArgumentException("Don't know how to extract properties object from $this") } +@Suppress("UNCHECKED_CAST") var EventTarget.tagProperty: Property - get() = properties.getOrPut("tornadofx.value") { - SimpleObjectProperty() - } as SimpleObjectProperty + get() = properties.getOrPut(VALUE_PROPERTY) { SimpleObjectProperty() } as SimpleObjectProperty set(value) { - properties["tornadofx.value"] = value + properties[VALUE_PROPERTY] = value } var EventTarget.tag: Any? @@ -62,112 +55,154 @@ var EventTarget.tag: Any? tagProperty.value = value } -@Deprecated("Properties set on the fake node would be lost. Do not use this function.", ReplaceWith("Manually adding children")) + +@Deprecated("Properties set on the fake node would be lost. Do not use this function. Add children manually instead.") fun children(addTo: MutableList, op: Pane.() -> Unit) { val fake = Pane().also(op) addTo.addAll(fake.children) } -fun EventTarget.text(initialValue: String? = null, op: Text.() -> Unit = {}) = Text().attachTo(this, op) { - if (initialValue != null) it.text = initialValue -} +// ================================================================ +// Text -fun EventTarget.text(property: Property, op: Text.() -> Unit = {}) = text().apply { - bind(property) - op(this) -} +fun EventTarget.text( + value: String? = null, + op: Text.() -> Unit = {} +): Text = Text().attachTo(this, op) { if (value != null) it.text = value } -fun EventTarget.text(observable: ObservableValue, op: Text.() -> Unit = {}) = text().apply { - bind(observable) - op(this) -} +fun EventTarget.text( + observable: ObservableValue, + op: Text.() -> Unit = {} +): Text = Text().attachTo(this, op) { it.bind(observable) } -fun EventTarget.textfield(value: String? = null, op: TextField.() -> Unit = {}) = TextField().attachTo(this, op) { - if (value != null) it.text = value -} +fun EventTarget.textflow(op: TextFlow.() -> Unit = {}): TextFlow = TextFlow().attachTo(this, op) -fun EventTarget.textfield(property: ObservableValue, op: TextField.() -> Unit = {}) = textfield().apply { - bind(property) - op(this) -} + +// ================================================================ +// TextField + +fun EventTarget.textfield( + value: String? = null, + op: TextField.() -> Unit = {} +): TextField = TextField().attachTo(this, op) { if (value != null) it.text = value } + +fun EventTarget.textfield( + observable: ObservableValue, + op: TextField.() -> Unit = {} +): TextField = TextField().attachTo(this, op) { it.bind(observable) } @JvmName("textfieldNumber") -fun EventTarget.textfield(property: ObservableValue, op: TextField.() -> Unit = {}) = textfield().apply { - bind(property) - op(this) -} +fun EventTarget.textfield( + observable: ObservableValue, + op: TextField.() -> Unit = {} +): TextField = TextField().attachTo(this, op) { it.bind(observable) } -fun EventTarget.passwordfield(value: String? = null, op: PasswordField.() -> Unit = {}) = PasswordField().attachTo(this, op) { - if (value != null) it.text = value +fun EventTarget.textfield( + property: Property, + converter: StringConverter, + op: TextField.() -> Unit = {} +): TextField = TextField().attachTo(this, op) { + it.textProperty().bindBidirectional(property, converter) + ViewModel.register(it.textProperty(), property) } -fun EventTarget.passwordfield(property: ObservableValue, op: PasswordField.() -> Unit = {}) = passwordfield().apply { - bind(property) - op(this) -} -fun EventTarget.textfield(property: Property, converter: StringConverter, op: TextField.() -> Unit = {}) = textfield().apply { - textProperty().bindBidirectional(property, converter) - ViewModel.register(textProperty(), property) - op(this) -} +// ================================================================ +// PasswordField -fun EventTarget.datepicker(op: DatePicker.() -> Unit = {}) = DatePicker().attachTo(this, op) -fun EventTarget.datepicker(property: Property, op: DatePicker.() -> Unit = {}) = datepicker().apply { - bind(property) - op(this) -} +fun EventTarget.passwordfield( + value: String? = null, + op: PasswordField.() -> Unit = {} +): PasswordField = PasswordField().attachTo(this, op) { if (value != null) it.text = value } -fun EventTarget.textarea(value: String? = null, op: TextArea.() -> Unit = {}) = TextArea().attachTo(this, op) { - if (value != null) it.text = value -} +fun EventTarget.passwordfield( + observable: ObservableValue, + op: PasswordField.() -> Unit = {} +): PasswordField = PasswordField().attachTo(this, op) { it.bind(observable) } -fun EventTarget.textarea(property: ObservableValue, op: TextArea.() -> Unit = {}) = textarea().apply { - bind(property) - op(this) -} -fun EventTarget.textarea(property: Property, converter: StringConverter, op: TextArea.() -> Unit = {}) = textarea().apply { - textProperty().bindBidirectional(property, converter) - ViewModel.register(textProperty(), property) - op(this) -} +// ================================================================ +// DatePicker -fun EventTarget.buttonbar(buttonOrder: String? = null, op: (ButtonBar.() -> Unit)) = ButtonBar().attachTo(this, op) { - if (buttonOrder != null) it.buttonOrder = buttonOrder -} +fun EventTarget.datepicker( + value: LocalDate? = null, + op: DatePicker.() -> Unit = {} +): DatePicker = DatePicker().attachTo(this, op) { if (value != null) it.value = value } -fun EventTarget.htmleditor(html: String? = null, op: HTMLEditor.() -> Unit = {}) = HTMLEditor().attachTo(this, op) { - if (html != null) it.htmlText = html -} +fun EventTarget.datepicker( + observable: ObservableValue, + op: DatePicker.() -> Unit = {} +): DatePicker = DatePicker().attachTo(this, op) { it.bind(observable) } -fun EventTarget.checkbox(text: String? = null, property: Property? = null, op: CheckBox.() -> Unit = {}) = CheckBox(text).attachTo(this, op) { - if (property != null) it.bind(property) -} -fun EventTarget.progressindicator(op: ProgressIndicator.() -> Unit = {}) = ProgressIndicator().attachTo(this, op) -fun EventTarget.progressindicator(property: Property, op: ProgressIndicator.() -> Unit = {}) = progressindicator().apply { - bind(property) - op(this) -} +// ================================================================ +// TextArea -fun EventTarget.progressbar(initialValue: Double? = null, op: ProgressBar.() -> Unit = {}) = ProgressBar().attachTo(this, op) { - if (initialValue != null) it.progress = initialValue -} +fun EventTarget.textarea( + value: String? = null, + op: TextArea.() -> Unit = {} +): TextArea = TextArea().attachTo(this, op) { if (value != null) it.text = value } -fun EventTarget.progressbar(property: ObservableValue, op: ProgressBar.() -> Unit = {}) = progressbar().apply { - bind(property) - op(this) +fun EventTarget.textarea( + observable: ObservableValue, + op: TextArea.() -> Unit = {} +): TextArea = TextArea().attachTo(this, op) { it.bind(observable) } + +fun EventTarget.textarea( + property: Property, + converter: StringConverter, + op: TextArea.() -> Unit = {} +): TextArea = TextArea().attachTo(this, op) { + it.textProperty().bindBidirectional(property, converter) + ViewModel.register(it.textProperty(), property) } + +fun EventTarget.buttonbar( + buttonOrder: String? = null, + op: ButtonBar.() -> Unit +): ButtonBar = ButtonBar().attachTo(this, op) { if (buttonOrder != null) it.buttonOrder = buttonOrder } + + +// ================================================================ +// ProgressIndicator + +fun EventTarget.progressindicator( + value: Double? = null, + op: ProgressIndicator.() -> Unit = {} +): ProgressIndicator = ProgressIndicator().attachTo(this, op) { if (value != null) it.progress = value } + +fun EventTarget.progressindicator( + observable: ObservableValue, + op: ProgressIndicator.() -> Unit = {} +): ProgressIndicator = ProgressIndicator().attachTo(this, op) { it.bind(observable) } + + +// ================================================================ +// ProgressBar + +fun EventTarget.progressbar( + value: Double? = null, + op: ProgressBar.() -> Unit = {} +): ProgressBar = ProgressBar().attachTo(this, op) { if (value != null) it.progress = value } + +fun EventTarget.progressbar( + observable: ObservableValue, + op: ProgressBar.() -> Unit = {} +): ProgressBar = ProgressBar().attachTo(this, op) { it.bind(observable) } + + +// ================================================================ +// Slider + fun EventTarget.slider( - min: Number? = null, - max: Number? = null, - value: Number? = null, - orientation: Orientation? = null, - op: Slider.() -> Unit = {} -) = Slider().attachTo(this, op) { + min: Number? = null, + max: Number? = null, + value: Number? = null, + orientation: Orientation? = null, + op: Slider.() -> Unit = {} +): Slider = Slider().attachTo(this, op) { if (min != null) it.min = min.toDouble() if (max != null) it.max = max.toDouble() if (value != null) it.value = value.toDouble() @@ -180,70 +215,184 @@ fun EventTarget.slider( orientation: Orientation? = null, op: Slider.() -> Unit = {} ): Slider - where T : Comparable, - T : Number { + where T : Number, + T : Comparable { return slider(range.start, range.endInclusive, value, orientation, op) } -// Buttons -fun EventTarget.button(text: String = "", graphic: Node? = null, op: Button.() -> Unit = {}) = Button(text).attachTo(this, op) { +// ================================================================ +// CheckBox + +fun EventTarget.checkbox( + text: String? = null, + observable: ObservableValue? = null, + op: CheckBox.() -> Unit = {} +): CheckBox = CheckBox(text).attachTo(this, op) { if (observable != null) it.bind(observable) } + + +// ================================================================ +// Button + +fun EventTarget.button( + text: String = "", // FIXME This parameter is not nullable but in all the other existing functions it is nullable + graphic: Node? = null, + op: Button.() -> Unit = {} +): Button = Button(text).attachTo(this, op) { if (graphic != null) it.graphic = graphic } -fun EventTarget.menubutton(text: String = "", graphic: Node? = null, op: MenuButton.() -> Unit = {}) = MenuButton(text).attachTo(this, op) { +fun EventTarget.button( + observable: ObservableValue, + graphic: Node? = null, + op: Button.() -> Unit = {} +): Button = Button().attachTo(this, op) { + it.textProperty().bind(observable) if (graphic != null) it.graphic = graphic } -fun EventTarget.button(text: ObservableValue, graphic: Node? = null, op: Button.() -> Unit = {}) = Button().attachTo(this, op) { - it.textProperty().bind(text) + +fun ToolBar.button( + text: String = "", + graphic: Node? = null, + op: Button.() -> Unit = {} +): Button = Button(text).attachTo(this, op) { if (graphic != null) it.graphic = graphic } -fun ToolBar.button(text: String = "", graphic: Node? = null, op: Button.() -> Unit = {}) = Button(text).also { +fun ToolBar.button( + observable: ObservableValue, + graphic: Node? = null, + op: Button.() -> Unit = {} +): Button = Button().attachTo(this, op) { + it.textProperty().bind(observable) if (graphic != null) it.graphic = graphic - items += it - op(it) } -fun ToolBar.button(text: ObservableValue, graphic: Node? = null, op: Button.() -> Unit = {}) = Button().also { - it.textProperty().bind(text) + +fun ButtonBar.button( + text: String = "", + type: ButtonBar.ButtonData? = null, + graphic: Node? = null, + op: Button.() -> Unit = {} +): Button = Button(text).attachTo(this, op) { + if (type != null) ButtonBar.setButtonData(it, type) if (graphic != null) it.graphic = graphic - items += it - op(it) } -fun ButtonBar.button(text: String = "", type: ButtonBar.ButtonData? = null, graphic: Node? = null, op: Button.() -> Unit = {}) = Button(text).also { +fun ButtonBar.button( + observable: ObservableValue, + type: ButtonBar.ButtonData? = null, + graphic: Node? = null, + op: Button.() -> Unit = {} +): Button = Button().attachTo(this, op) { + it.textProperty().bind(observable) if (type != null) ButtonBar.setButtonData(it, type) if (graphic != null) it.graphic = graphic - buttons += it - op(it) } -fun ButtonBar.button(text: ObservableValue, type: ButtonBar.ButtonData? = null, graphic: Node? = null, op: Button.() -> Unit = {}) = Button().also { - it.textProperty().bind(text) - if (type != null) ButtonBar.setButtonData(it, type) + +fun EventTarget.menubutton( + text: String = "", + graphic: Node? = null, + op: MenuButton.() -> Unit = {} +): MenuButton = MenuButton(text).attachTo(this, op) { + if (graphic != null) it.graphic = graphic +} + +fun EventTarget.menubutton( + observable: ObservableValue, + graphic: Node? = null, + op: MenuButton.() -> Unit = {} +): MenuButton = MenuButton().attachTo(this, op) { + it.textProperty().bind(observable) if (graphic != null) it.graphic = graphic - buttons += it - op(it) } -fun Node.togglegroup(op: ToggleGroup.() -> Unit = {}) = ToggleGroup().also { - properties["tornadofx.togglegroup"] = it - op(it) + +/** + * Create a [ToggleGroup] inside the current or given toggle group. The optional value parameter will be matched against + * the extension property `selectedValueProperty()` on Toggle Group. If the [ToggleGroup.selectedValueProperty] is used, + * it's value will be updated to reflect the value for this radio button when it's selected. + * + * Likewise, if the `selectedValueProperty` of the ToggleGroup is updated to a value that matches the value for this + * ToggleGroup, it will be automatically selected. + */ +fun EventTarget.togglebutton( + text: String? = null, + group: ToggleGroup? = getToggleGroup(), + selectFirst: Boolean = true, + value: Any? = null, + op: ToggleButton.() -> Unit = {} +): ToggleButton = ToggleButton().attachTo(this, op) { + it.properties[TOGGLE_GROUP_VALUE_PROPERTY] = value ?: text + if (text != null || value != null) it.text = text ?: value.toString() + if (group != null) it.toggleGroup = group + if (it.toggleGroup?.selectedToggle == null && selectFirst) it.isSelected = true +} + +fun EventTarget.togglebutton( + observable: ObservableValue, + group: ToggleGroup? = getToggleGroup(), + selectFirst: Boolean = true, + value: Any? = null, + op: ToggleButton.() -> Unit = {} +): ToggleButton = ToggleButton().attachTo(this, op) { + it.properties[TOGGLE_GROUP_VALUE_PROPERTY] = value ?: observable + it.textProperty().bind(observable) + if (group != null) it.toggleGroup = group + if (it.toggleGroup?.selectedToggle == null && selectFirst) it.isSelected = true +} + + +/** + * Create a [RadioButton] inside the current or given toggle group. The optional value parameter will be matched against + * the extension property `selectedValueProperty()` on Toggle Group. If the [ToggleGroup.selectedValueProperty] is used, + * it's value will be updated to reflect the value for this radio button when it's selected. + * + * Likewise, if the `selectedValueProperty` of the ToggleGroup is updated to a value that matches the value for this + * RadioButton, it will be automatically selected. + */ +fun EventTarget.radiobutton( + text: String? = null, + group: ToggleGroup? = getToggleGroup(), + value: Any? = null, + op: RadioButton.() -> Unit = {} +): RadioButton = RadioButton().attachTo(this, op) { + it.properties[TOGGLE_GROUP_VALUE_PROPERTY] = value ?: text + if (text != null || value != null) it.text = text ?: value.toString() + if (group != null) it.toggleGroup = group +} + +fun EventTarget.radiobutton( + observable: ObservableValue, + group: ToggleGroup? = getToggleGroup(), + value: Any? = null, + op: RadioButton.() -> Unit = {} +): RadioButton = RadioButton().attachTo(this, op) { + it.properties[TOGGLE_GROUP_VALUE_PROPERTY] = value ?: observable + it.textProperty().bind(observable) + if (group != null) it.toggleGroup = group } + +// ================================================================ +// ToggleGroup + +fun Node.togglegroup(op: ToggleGroup.() -> Unit = {}): ToggleGroup = ToggleGroup().also { properties[TOGGLE_GROUP_PROPERTY] = it }.also(op) + /** - * Bind the selectedValueProperty of this toggle group to the given property. Passing in a writeable value + * Bind the selectedValueProperty of this toggle group to the given property. Passing in a writable value * will result in a bidirectional binding, while passing in a read only value will result in a unidirectional binding. * * If the toggles are configured with the value parameter (@see #togglebutton and #radiogroup), the corresponding * button will be selected when the value is changed. Likewise, if the selected toggle is changed, - * the property value will be updated if it is writeable. + * the property value will be updated if it is writable. */ -fun ToggleGroup.bind(property: ObservableValue) = selectedValueProperty().apply { - (property as? Property)?.also { bindBidirectional(it) } - ?: bind(property) +fun ToggleGroup.bind(observable: ObservableValue) { + selectedValueProperty().apply { + if (observable is Property) bindBidirectional(observable) else bind(observable) + } } /** @@ -254,84 +403,38 @@ fun ToggleGroup.bind(property: ObservableValue) = selectedValueProperty ToggleGroup.selectedValueProperty(): ObjectProperty = properties.getOrPut("tornadofx.selectedValueProperty") { +@Suppress("UNCHECKED_CAST") +fun ToggleGroup.selectedValueProperty(): ObjectProperty = properties.getOrPut(SELECTED_VALUE_PROPERTY) { SimpleObjectProperty().apply { selectedToggleProperty().onChange { - value = it?.properties?.get("tornadofx.toggleGroupValue") as T? + value = it?.properties?.get(TOGGLE_GROUP_VALUE_PROPERTY) as T? } onChange { selectedValue -> - selectToggle(toggles.find { it.properties["tornadofx.toggleGroupValue"] == selectedValue }) + selectToggle(toggles.find { it.properties[TOGGLE_GROUP_VALUE_PROPERTY] == selectedValue }) } } } as ObjectProperty -/** - * Create a togglebutton inside the current or given toggle group. The optional value parameter will be matched against - * the extension property `selectedValueProperty()` on Toggle Group. If the #ToggleGroup.selectedValueProperty is used, - * it's value will be updated to reflect the value for this radio button when it's selected. - * - * Likewise, if the `selectedValueProperty` of the ToggleGroup is updated to a value that matches the value for this - * togglebutton, it will be automatically selected. - */ -fun EventTarget.togglebutton( - text: String? = null, - group: ToggleGroup? = getToggleGroup(), - selectFirst: Boolean = true, - value: Any? = null, - op: ToggleButton.() -> Unit = {} -) = ToggleButton().attachTo(this, op) { - it.text = if (value != null && text == null) value.toString() else text ?: "" - it.properties["tornadofx.toggleGroupValue"] = value ?: text - if (group != null) it.toggleGroup = group - if (it.toggleGroup?.selectedToggle == null && selectFirst) it.isSelected = true -} - -fun EventTarget.togglebutton( - text: ObservableValue? = null, - group: ToggleGroup? = getToggleGroup(), - selectFirst: Boolean = true, - value: Any? = null, - op: ToggleButton.() -> Unit = {} -) = ToggleButton().attachTo(this, op) { - it.textProperty().bind(text) - it.properties["tornadofx.toggleGroupValue"] = value ?: text - if (group != null) it.toggleGroup = group - if (it.toggleGroup?.selectedToggle == null && selectFirst) it.isSelected = true -} - fun ToggleButton.whenSelected(op: () -> Unit) { selectedProperty().onChange { if (it) op() } } -/** - * Create a radiobutton inside the current or given toggle group. The optional value parameter will be matched against - * the extension property `selectedValueProperty()` on Toggle Group. If the #ToggleGroup.selectedValueProperty is used, - * it's value will be updated to reflect the value for this radio button when it's selected. - * - * Likewise, if the `selectedValueProperty` of the ToggleGroup is updated to a value that matches the value for this - * radiobutton, it will be automatically selected. - */ -fun EventTarget.radiobutton( - text: String? = null, - group: ToggleGroup? = getToggleGroup(), - value: Any? = null, - op: RadioButton.() -> Unit = {} -) = RadioButton().attachTo(this, op) { - it.text = if (value != null && text == null) value.toString() else text ?: "" - it.properties["tornadofx.toggleGroupValue"] = value ?: text - if (group != null) it.toggleGroup = group -} -fun EventTarget.label(text: String = "", graphic: Node? = null, op: Label.() -> Unit = {}) = Label(text).attachTo(this, op) { - if (graphic != null) it.graphic = graphic -} +// ================================================================ +// Label + +fun EventTarget.label( + text: String? = "", + graphic: Node? = null, + op: Label.() -> Unit = {} +): Label = Label(text).attachTo(this, op) { if (graphic != null) it.graphic = graphic } inline fun EventTarget.label( - observable: ObservableValue, - graphicProperty: ObjectProperty? = null, - converter: StringConverter? = null, - noinline op: Label.() -> Unit = {} -) = label().apply { + observable: ObservableValue, + graphicProperty: ObservableValue? = null, // FIXME Inconsistent with all the other functions. There is no other function with ObservableValue + converter: StringConverter? = null, + noinline op: Label.() -> Unit = {} +): Label = label().apply { if (converter == null) { if (T::class == String::class) { @Suppress("UNCHECKED_CAST") @@ -346,63 +449,135 @@ inline fun EventTarget.label( op(this) } -fun EventTarget.hyperlink(text: String = "", graphic: Node? = null, op: Hyperlink.() -> Unit = {}) = Hyperlink(text, graphic).attachTo(this, op) -fun EventTarget.hyperlink(observable: ObservableValue, graphic: Node? = null, op: Hyperlink.() -> Unit = {}) = hyperlink(graphic = graphic).apply { - bind(observable) - op(this) + +// ================================================================ +// WebView + +fun EventTarget.webview(op: WebView.() -> Unit = {}): WebView = WebView().attachTo(this, op) + + +// ================================================================ +// Hyperlink + +fun EventTarget.hyperlink( + text: String = "", + graphic: Node? = null, + op: Hyperlink.() -> Unit = {} +): Hyperlink = Hyperlink(text).attachTo(this, op) { + if (graphic != null) it.graphic = graphic } -fun EventTarget.menubar(op: MenuBar.() -> Unit = {}) = MenuBar().attachTo(this, op) +fun EventTarget.hyperlink( + observable: ObservableValue, + graphic: Node? = null, + op: Hyperlink.() -> Unit = {} +): Hyperlink = Hyperlink().attachTo(this, op) { + it.bind(observable) + if (graphic != null) it.graphic = graphic +} -fun EventTarget.imageview(url: String? = null, lazyload: Boolean = true, op: ImageView.() -> Unit = {}) = - opcr(this, if (url == null) ImageView() else ImageView(Image(url, lazyload)), op) -fun EventTarget.imageview( - url: ObservableValue, - lazyload: Boolean = true, - op: ImageView.() -> Unit = {} -) = ImageView().attachTo(this, op) { imageView -> - imageView.imageProperty().bind(objectBinding(url) { value?.let { Image(it, lazyload) } }) +// ================================================================ +// HTMLEditor + +fun EventTarget.htmleditor( + html: String? = null, + op: HTMLEditor.() -> Unit = {} +): HTMLEditor = HTMLEditor().attachTo(this, op) { if (html != null) it.htmlText = html } + + +// ================================================================ +// ColorPicker + +fun EventTarget.colorpicker( + color: Color? = null, + mode: ColorPickerMode = ColorPickerMode.Button, + op: ColorPicker.() -> Unit = {} +): ColorPicker = ColorPicker().attachTo(this, op) { + if (mode == ColorPickerMode.MenuButton) it.addClass(ColorPicker.STYLE_CLASS_BUTTON) + else if (mode == ColorPickerMode.SplitMenuButton) it.addClass(ColorPicker.STYLE_CLASS_SPLIT_BUTTON) + if (color != null) it.value = color } -fun EventTarget.imageview(image: ObservableValue, op: ImageView.() -> Unit = {}) = ImageView().attachTo(this, op) { - it.imageProperty().bind(image) +enum class ColorPickerMode { Button, MenuButton, SplitMenuButton } + + +// ================================================================ +// MenuBar + +fun EventTarget.menubar(op: MenuBar.() -> Unit = {}): MenuBar = MenuBar().attachTo(this, op) + + +// ================================================================ +// ImageView + +fun EventTarget.imageview( + url: String? = null, + lazyload: Boolean = true, + op: ImageView.() -> Unit = {} +): ImageView = ImageView().attachTo(this, op) { if (url != null) it.image = Image(url, lazyload) } + +fun EventTarget.imageview( + observableUrl: ObservableValue, + lazyload: Boolean = true, + op: ImageView.() -> Unit = {} +): ImageView = ImageView().attachTo(this, op) { + it.imageProperty().bind(objectBinding(observableUrl) { value?.let { url -> Image(url, lazyload) } }) } -fun EventTarget.imageview(image: Image, op: ImageView.() -> Unit = {}) = ImageView(image).attachTo(this, op) +fun EventTarget.imageview( + image: Image, + op: ImageView.() -> Unit = {} +): ImageView = ImageView(image).attachTo(this, op) -/** - * Listen to changes and update the value of the property if the given mutator results in a different value - */ -fun Property.mutateOnChange(mutator: (T?) -> T?) = onChange { - val changed = mutator(value) - if (changed != value) value = changed +fun EventTarget.imageview( + observableImage: ObservableValue, + op: ImageView.() -> Unit = {} +): ImageView = ImageView().attachTo(this, op) { it.imageProperty().bind(observableImage) } + + +// ================================================================ +// Utility Functions + +/** Listen to changes and update the value of the property if the given mutator results in a different value. */ +fun Property.mutateOnChange(mutator: (T?) -> T?) { + onChange { + val changed = mutator(value) + if (changed != value) value = changed + } } -/** - * Remove leading or trailing whitespace from a Text Input Control. - */ -fun TextInputControl.trimWhitespace() = focusedProperty().onChange { focused -> - if (!focused && text != null) text = text.trim() +/** Remove leading or trailing whitespace from a Text Input Control. */ +fun TextInputControl.trimWhitespace() { + focusedProperty().onChange { focused -> + if (!focused && text != null) text = text.trim() + } } -/** - * Remove any whitespace from a Text Input Control. - */ -fun TextInputControl.stripWhitespace() = textProperty().mutateOnChange { it?.replace(Regex("\\s*"), "") } +/** Remove any whitespace from a Text Input Control. */ +fun TextInputControl.stripWhitespace(): Unit = + textProperty().mutateOnChange { it?.replace(Regex("\\s*"), "") } -/** - * Remove any non integer values from a Text Input Control. - */ -fun TextInputControl.stripNonInteger() = textProperty().mutateOnChange { it?.replace(Regex("[^0-9]"), "") } +/** Remove any non integer values from a Text Input Control. */ +fun TextInputControl.stripNonInteger(): Unit = + textProperty().mutateOnChange { it?.replace(Regex("[^0-9]"), "") } + +@Deprecated("Use the variant with chars instead") +fun TextInputControl.stripNonNumeric(vararg allowedChars: String = arrayOf(".", ",")): Unit = stripNonNumeric(*allowedChars.joinToString("").toCharArray()) /** - * Remove any non integer values from a Text Input Control. + * Remove any non numeric values from a Text Input Control. + * + * Additional [allowed chars][allowedChars] can be specified. */ -fun TextInputControl.stripNonNumeric(vararg allowedChars: String = arrayOf(".", ",")) = - textProperty().mutateOnChange { it?.replace(Regex("[^0-9${allowedChars.joinToString("")}]"), "") } +fun TextInputControl.stripNonNumeric(vararg allowedChars: Char = charArrayOf('.', ',')) { + textProperty().mutateOnChange { text -> + val chars = allowedChars.asSequence().filter { it !in '0'..'9' }.distinct().sorted().joinToString("") + text?.replace(Regex("[^0-9$chars]"), "") + } +} -fun ChoiceBox<*>.action(op: () -> Unit) = setOnAction { op() } -fun ButtonBase.action(op: () -> Unit) = setOnAction { op() } -fun TextField.action(op: () -> Unit) = setOnAction { op() } -fun MenuItem.action(op: () -> Unit) = setOnAction { op() } +fun MenuItem.action(op: () -> Unit): Unit = setOnAction { op() } +fun TextField.action(op: () -> Unit): Unit = setOnAction { op() } +fun ButtonBase.action(op: () -> Unit): Unit = setOnAction { op() } +fun ChoiceBox<*>.action(op: () -> Unit): Unit = setOnAction { op() } diff --git a/src/main/java/tornadofx/Menu.kt b/src/main/java/tornadofx/Menu.kt index 55a048a1d..0dc16112e 100644 --- a/src/main/java/tornadofx/Menu.kt +++ b/src/main/java/tornadofx/Menu.kt @@ -228,7 +228,7 @@ fun Menu.radiomenuitem( ) = RadioMenuItem(name, graphic).also { toggleGroup?.apply { it.toggleGroup = this } keyCombination?.apply { it.accelerator = this } - properties["tornadofx.toggleGroupValue"] = value ?: text + properties[TOGGLE_GROUP_VALUE_PROPERTY] = value ?: text graphic?.apply { it.graphic = graphic } op(it) this += it @@ -254,7 +254,7 @@ fun MenuButton.radiomenuitem( ) = RadioMenuItem(name, graphic).also { toggleGroup?.apply { it.toggleGroup = this } keyCombination?.apply { it.accelerator = this } - properties["tornadofx.toggleGroupValue"] = value ?: text + properties[TOGGLE_GROUP_VALUE_PROPERTY] = value ?: text graphic?.apply { it.graphic = graphic } op(it) items += it @@ -305,7 +305,7 @@ fun ContextMenu.radiomenuitem( ) = RadioMenuItem(name, graphic).also { toggleGroup?.apply { it.toggleGroup = this } keyCombination?.apply { it.accelerator = this } - properties["tornadofx.toggleGroupValue"] = value ?: name + properties[TOGGLE_GROUP_VALUE_PROPERTY] = value ?: name graphic?.apply { it.graphic = graphic } op(it) this += it diff --git a/src/main/java/tornadofx/Nodes.kt b/src/main/java/tornadofx/Nodes.kt index 144209473..a49a04c83 100644 --- a/src/main/java/tornadofx/Nodes.kt +++ b/src/main/java/tornadofx/Nodes.kt @@ -140,7 +140,7 @@ fun T.toggleClass(className: String, predicate: Boolean) = apply { } } -fun EventTarget.getToggleGroup(): ToggleGroup? = properties["tornadofx.togglegroup"] as ToggleGroup? +fun EventTarget.getToggleGroup(): ToggleGroup? = properties[TOGGLE_GROUP_PROPERTY] as ToggleGroup? fun Node.tooltip(text: String? = null, graphic: Node? = null, op: Tooltip.() -> Unit = {}): Tooltip { val newToolTip = Tooltip(text) From f8d10a45b924da13fdf16aba68895d77fd49e570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Gonz=C3=A1lez=20Abril?= Date: Sun, 28 Oct 2018 23:59:28 +0100 Subject: [PATCH 2/5] Fixed bug in `label()` function where graphicProperty was to correctly checked. --- src/main/java/tornadofx/Controls.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/tornadofx/Controls.kt b/src/main/java/tornadofx/Controls.kt index 50ac98131..a1f7c68da 100644 --- a/src/main/java/tornadofx/Controls.kt +++ b/src/main/java/tornadofx/Controls.kt @@ -445,7 +445,7 @@ inline fun EventTarget.label( } else { textProperty().bind(observable.stringBinding { converter.toString(it) }) } - if (graphic != null) graphicProperty().bind(graphicProperty) + if (graphicProperty != null) graphicProperty().bind(graphicProperty) op(this) } From bcefabba7b01aab4a769c01d68d501964ea2c05d Mon Sep 17 00:00:00 2001 From: Thijs de Haan Date: Tue, 30 Oct 2018 14:02:01 +0100 Subject: [PATCH 3/5] Update src/main/java/tornadofx/Controls.kt Co-Authored-By: SackCastellon --- src/main/java/tornadofx/Controls.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/tornadofx/Controls.kt b/src/main/java/tornadofx/Controls.kt index a1f7c68da..ebb25333e 100644 --- a/src/main/java/tornadofx/Controls.kt +++ b/src/main/java/tornadofx/Controls.kt @@ -379,7 +379,9 @@ fun EventTarget.radiobutton( // ================================================================ // ToggleGroup -fun Node.togglegroup(op: ToggleGroup.() -> Unit = {}): ToggleGroup = ToggleGroup().also { properties[TOGGLE_GROUP_PROPERTY] = it }.also(op) +fun Node.togglegroup(op: ToggleGroup.() -> Unit = {}): ToggleGroup = ToggleGroup().also { + properties[TOGGLE_GROUP_PROPERTY] = it +}.also(op) /** * Bind the selectedValueProperty of this toggle group to the given property. Passing in a writable value From b0bb9e5507b8444c80c442ad7bc21d6f33b35f30 Mon Sep 17 00:00:00 2001 From: Thijs de Haan Date: Tue, 30 Oct 2018 14:02:28 +0100 Subject: [PATCH 4/5] Update src/main/java/tornadofx/Controls.kt Co-Authored-By: SackCastellon --- src/main/java/tornadofx/Controls.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/tornadofx/Controls.kt b/src/main/java/tornadofx/Controls.kt index ebb25333e..4534b9c05 100644 --- a/src/main/java/tornadofx/Controls.kt +++ b/src/main/java/tornadofx/Controls.kt @@ -565,7 +565,9 @@ fun TextInputControl.stripNonInteger(): Unit = textProperty().mutateOnChange { it?.replace(Regex("[^0-9]"), "") } @Deprecated("Use the variant with chars instead") -fun TextInputControl.stripNonNumeric(vararg allowedChars: String = arrayOf(".", ",")): Unit = stripNonNumeric(*allowedChars.joinToString("").toCharArray()) +fun TextInputControl.stripNonNumeric(vararg allowedChars: String = arrayOf(".", ",")){ + stripNonNumeric(*allowedChars.joinToString("").toCharArray()) +} /** * Remove any non numeric values from a Text Input Control. From 5e5e0c2a1e0f31ea9554d1bbc374434bf51f215e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Gonz=C3=A1lez=20Abril?= Date: Tue, 30 Oct 2018 14:32:47 +0100 Subject: [PATCH 5/5] Update CHANGELOG.md and add some line breaks. --- CHANGELOG.md | 7 +++ src/main/java/tornadofx/Controls.kt | 88 +++++++++++++++++++++-------- 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a67ad003..b16f44bf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ - The Workspace inside the `scope` of a UIComponents will assume the Workspace it is docked in (https://github.com/edvin/tornadofx/issues/806) - Kotlin 1.2.70 - bindSelected for ViewModel gets `out` modifier (https://github.com/edvin/tornadofx/issues/823) +- Added support for more classes in `EventTarget.properties`. +- Extracted (some) property keys to top level constants. + - Every hardcoded `"tornadofx.someName"` is now `const val SOME_NAME_PROPERTY = "tornadofx.someName"` +- Generalized (some) `Property` to `ObservableValue`. +- `TextInputControl.stripNonNumeric()` now accepts Chars instead of Strings. ### Additions @@ -21,6 +26,8 @@ - Media.play() shortcut which creates a MediaPlayer and plays the Media - Wipe and Dissolve view transitions - `tab` builder assigns `UIComponent.icon` as Tab graphic +- Added some function variations with `ObservableValue` instead of `T`. + - For an existing `fun build(s: String)` a new variant `fun build(s: ObservableValue)` is added. ## [1.7.17] diff --git a/src/main/java/tornadofx/Controls.kt b/src/main/java/tornadofx/Controls.kt index 4534b9c05..0303aa4ed 100644 --- a/src/main/java/tornadofx/Controls.kt +++ b/src/main/java/tornadofx/Controls.kt @@ -38,7 +38,7 @@ internal val EventTarget.properties: ObservableMap is MenuItem -> properties is Toggle -> properties is ToggleGroup -> properties - is TableColumnBase<*, *> -> this.properties // Explicit this is required due to weird SmartCast + is TableColumnBase<*, *> -> this.properties // Explicit `this` is required due to weird SmartCast else -> throw IllegalArgumentException("Don't know how to extract properties object from $this") } @@ -69,12 +69,16 @@ fun children(addTo: MutableList, op: Pane.() -> Unit) { fun EventTarget.text( value: String? = null, op: Text.() -> Unit = {} -): Text = Text().attachTo(this, op) { if (value != null) it.text = value } +): Text = Text().attachTo(this, op) { + if (value != null) it.text = value +} fun EventTarget.text( observable: ObservableValue, op: Text.() -> Unit = {} -): Text = Text().attachTo(this, op) { it.bind(observable) } +): Text = Text().attachTo(this, op) { + it.bind(observable) +} fun EventTarget.textflow(op: TextFlow.() -> Unit = {}): TextFlow = TextFlow().attachTo(this, op) @@ -85,18 +89,24 @@ fun EventTarget.textflow(op: TextFlow.() -> Unit = {}): TextFlow = TextFlow().at fun EventTarget.textfield( value: String? = null, op: TextField.() -> Unit = {} -): TextField = TextField().attachTo(this, op) { if (value != null) it.text = value } +): TextField = TextField().attachTo(this, op) { + if (value != null) it.text = value +} fun EventTarget.textfield( observable: ObservableValue, op: TextField.() -> Unit = {} -): TextField = TextField().attachTo(this, op) { it.bind(observable) } +): TextField = TextField().attachTo(this, op) { + it.bind(observable) +} @JvmName("textfieldNumber") fun EventTarget.textfield( observable: ObservableValue, op: TextField.() -> Unit = {} -): TextField = TextField().attachTo(this, op) { it.bind(observable) } +): TextField = TextField().attachTo(this, op) { + it.bind(observable) +} fun EventTarget.textfield( property: Property, @@ -114,12 +124,16 @@ fun EventTarget.textfield( fun EventTarget.passwordfield( value: String? = null, op: PasswordField.() -> Unit = {} -): PasswordField = PasswordField().attachTo(this, op) { if (value != null) it.text = value } +): PasswordField = PasswordField().attachTo(this, op) { + if (value != null) it.text = value +} fun EventTarget.passwordfield( observable: ObservableValue, op: PasswordField.() -> Unit = {} -): PasswordField = PasswordField().attachTo(this, op) { it.bind(observable) } +): PasswordField = PasswordField().attachTo(this, op) { + it.bind(observable) +} // ================================================================ @@ -128,12 +142,16 @@ fun EventTarget.passwordfield( fun EventTarget.datepicker( value: LocalDate? = null, op: DatePicker.() -> Unit = {} -): DatePicker = DatePicker().attachTo(this, op) { if (value != null) it.value = value } +): DatePicker = DatePicker().attachTo(this, op) { + if (value != null) it.value = value +} fun EventTarget.datepicker( observable: ObservableValue, op: DatePicker.() -> Unit = {} -): DatePicker = DatePicker().attachTo(this, op) { it.bind(observable) } +): DatePicker = DatePicker().attachTo(this, op) { + it.bind(observable) +} // ================================================================ @@ -142,12 +160,16 @@ fun EventTarget.datepicker( fun EventTarget.textarea( value: String? = null, op: TextArea.() -> Unit = {} -): TextArea = TextArea().attachTo(this, op) { if (value != null) it.text = value } +): TextArea = TextArea().attachTo(this, op) { + if (value != null) it.text = value +} fun EventTarget.textarea( observable: ObservableValue, op: TextArea.() -> Unit = {} -): TextArea = TextArea().attachTo(this, op) { it.bind(observable) } +): TextArea = TextArea().attachTo(this, op) { + it.bind(observable) +} fun EventTarget.textarea( property: Property, @@ -162,7 +184,9 @@ fun EventTarget.textarea( fun EventTarget.buttonbar( buttonOrder: String? = null, op: ButtonBar.() -> Unit -): ButtonBar = ButtonBar().attachTo(this, op) { if (buttonOrder != null) it.buttonOrder = buttonOrder } +): ButtonBar = ButtonBar().attachTo(this, op) { + if (buttonOrder != null) it.buttonOrder = buttonOrder +} // ================================================================ @@ -171,12 +195,16 @@ fun EventTarget.buttonbar( fun EventTarget.progressindicator( value: Double? = null, op: ProgressIndicator.() -> Unit = {} -): ProgressIndicator = ProgressIndicator().attachTo(this, op) { if (value != null) it.progress = value } +): ProgressIndicator = ProgressIndicator().attachTo(this, op) { + if (value != null) it.progress = value +} fun EventTarget.progressindicator( observable: ObservableValue, op: ProgressIndicator.() -> Unit = {} -): ProgressIndicator = ProgressIndicator().attachTo(this, op) { it.bind(observable) } +): ProgressIndicator = ProgressIndicator().attachTo(this, op) { + it.bind(observable) +} // ================================================================ @@ -185,12 +213,16 @@ fun EventTarget.progressindicator( fun EventTarget.progressbar( value: Double? = null, op: ProgressBar.() -> Unit = {} -): ProgressBar = ProgressBar().attachTo(this, op) { if (value != null) it.progress = value } +): ProgressBar = ProgressBar().attachTo(this, op) { + if (value != null) it.progress = value +} fun EventTarget.progressbar( observable: ObservableValue, op: ProgressBar.() -> Unit = {} -): ProgressBar = ProgressBar().attachTo(this, op) { it.bind(observable) } +): ProgressBar = ProgressBar().attachTo(this, op) { + it.bind(observable) +} // ================================================================ @@ -228,7 +260,9 @@ fun EventTarget.checkbox( text: String? = null, observable: ObservableValue? = null, op: CheckBox.() -> Unit = {} -): CheckBox = CheckBox(text).attachTo(this, op) { if (observable != null) it.bind(observable) } +): CheckBox = CheckBox(text).attachTo(this, op) { + if (observable != null) it.bind(observable) +} // ================================================================ @@ -429,7 +463,9 @@ fun EventTarget.label( text: String? = "", graphic: Node? = null, op: Label.() -> Unit = {} -): Label = Label(text).attachTo(this, op) { if (graphic != null) it.graphic = graphic } +): Label = Label(text).attachTo(this, op) { + if (graphic != null) it.graphic = graphic +} inline fun EventTarget.label( observable: ObservableValue, @@ -485,7 +521,9 @@ fun EventTarget.hyperlink( fun EventTarget.htmleditor( html: String? = null, op: HTMLEditor.() -> Unit = {} -): HTMLEditor = HTMLEditor().attachTo(this, op) { if (html != null) it.htmlText = html } +): HTMLEditor = HTMLEditor().attachTo(this, op) { + if (html != null) it.htmlText = html +} // ================================================================ @@ -517,7 +555,9 @@ fun EventTarget.imageview( url: String? = null, lazyload: Boolean = true, op: ImageView.() -> Unit = {} -): ImageView = ImageView().attachTo(this, op) { if (url != null) it.image = Image(url, lazyload) } +): ImageView = ImageView().attachTo(this, op) { + if (url != null) it.image = Image(url, lazyload) +} fun EventTarget.imageview( observableUrl: ObservableValue, @@ -535,7 +575,9 @@ fun EventTarget.imageview( fun EventTarget.imageview( observableImage: ObservableValue, op: ImageView.() -> Unit = {} -): ImageView = ImageView().attachTo(this, op) { it.imageProperty().bind(observableImage) } +): ImageView = ImageView().attachTo(this, op) { + it.imageProperty().bind(observableImage) +} // ================================================================ @@ -565,7 +607,7 @@ fun TextInputControl.stripNonInteger(): Unit = textProperty().mutateOnChange { it?.replace(Regex("[^0-9]"), "") } @Deprecated("Use the variant with chars instead") -fun TextInputControl.stripNonNumeric(vararg allowedChars: String = arrayOf(".", ",")){ +fun TextInputControl.stripNonNumeric(vararg allowedChars: String = arrayOf(".", ",")) { stripNonNumeric(*allowedChars.joinToString("").toCharArray()) }