All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tornadofx.Component.kt Maven / Gradle / Ivy

There is a newer version: 1.7.20
Show newest version
package tornadofx

import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections
import javafx.collections.ObservableMap
import javafx.concurrent.Task
import javafx.event.EventTarget
import javafx.fxml.FXMLLoader
import javafx.scene.Node
import javafx.scene.Parent
import javafx.scene.Scene
import javafx.scene.input.KeyCode
import javafx.scene.input.KeyEvent
import javafx.stage.Modality
import javafx.stage.Stage
import javafx.stage.StageStyle
import javafx.stage.Window
import java.net.URL
import java.nio.file.Files
import java.nio.file.Paths
import java.util.*
import java.util.logging.Logger
import kotlin.concurrent.thread
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

interface Injectable

abstract class Component {
    val config: Properties
        get() = _config.value

    val properties: ObservableMap
        get() = _properties.value

    fun Properties.set(pair: Pair) = set(pair.first, pair.second?.toString())
    fun Properties.string(key: String, defaultValue: String? = null) = config.getProperty(key, defaultValue)
    fun Properties.boolean(key: String) = config.getProperty(key)?.toBoolean() ?: false
    fun Properties.double(key: String) = config.getProperty(key)?.toDouble()
    fun Properties.save() = Files.newOutputStream(configPath.value).use { output -> store(output, "") }

    private val _config = lazy {
        Properties().apply {
            if (Files.exists(configPath.value))
                Files.newInputStream(configPath.value).use { load(it) }
        }
    }

    private val _properties = lazy { FXCollections.observableHashMap() }
    private val _log = lazy { Logger.getLogger([email protected]) }
    val log: Logger get() = _log.value

    private val configPath = lazy {
        val conf = Paths.get("conf")
        if (!Files.exists(conf))
            Files.createDirectories(conf)
        conf.resolve(javaClass.name + ".properties")
    }

    private val _messages: SimpleObjectProperty = object : SimpleObjectProperty() {
        override fun get(): ResourceBundle? {
            if (super.get() == null) {
                try {
                    val bundle = ResourceBundle.getBundle([email protected], FX.locale, FXResourceBundleControl.INSTANCE)
                    if (bundle is FXPropertyResourceBundle)
                        bundle.inheritFromGlobal()
                    set(bundle)
                } catch (ex: Exception) {
                    FX.log.fine({ "No Messages found for ${javaClass.name} in locale ${FX.locale}, using global bundle" })
                    set(FX.messages)
                }
            }
            return super.get()
        }
    }

    var messages: ResourceBundle
        get() = _messages.get()
        set(value) = _messages.set(value)

    val resources: ResourceLookup by lazy {
        ResourceLookup(this)
    }

    inline fun  inject(): ReadOnlyProperty = object : ReadOnlyProperty {
        override fun getValue(thisRef: Component, property: KProperty<*>) = find(T::class)
    }

    inline fun  fragment(): ReadOnlyProperty = object : ReadOnlyProperty {
        var fragment: T? = null

        override fun getValue(thisRef: Component, property: KProperty<*>): T {
            if (fragment == null) fragment = findFragment(T::class)
            return fragment!!
        }
    }

    inline fun  di(): ReadOnlyProperty = object : ReadOnlyProperty {
        override fun getValue(thisRef: Component, property: KProperty<*>) = FX.dicontainer?.let { it.getInstance(T::class) } ?: throw AssertionError("Injector is not configured, so bean of type ${T::class} can not be resolved")
    }

    val primaryStage: Stage get() = FX.primaryStage

    @Deprecated("Clashes with Region.background, so runAsync is a better name", ReplaceWith("runAsync"), DeprecationLevel.WARNING)
    fun  background(func: () -> T) = task(func)

    fun  runAsync(func: () -> T) = task(func)
    infix fun  Task.ui(func: (T) -> Unit) = success(func)
}

abstract class Controller : Component(), Injectable

abstract class UIComponent : Component() {
    var fxmlLoader: FXMLLoader? = null
    var modalStage: Stage? = null
    abstract val root: Parent

    fun init() {
        root.properties["tornadofx.uicomponent"] = this
    }

    fun openModal(stageStyle: StageStyle = StageStyle.DECORATED, modality: Modality = Modality.APPLICATION_MODAL, escapeClosesWindow: Boolean = true, owner: Window? = null, block: Boolean = false) {
        if (modalStage == null) {
            if (root !is Parent) {
                throw IllegalArgumentException("Only Parent Fragments can be opened in a Modal")
            } else {
                modalStage = Stage(stageStyle).apply {
                    titleProperty().bind(titleProperty)
                    initModality(modality)
                    if (owner != null) initOwner(owner)

                    if (root.scene != null) {
                        scene = root.scene
                        [email protected]["tornadofx.scene"] = root.scene
                    } else {

                        Scene(root).apply {
                            if (escapeClosesWindow) {
                                addEventFilter(KeyEvent.KEY_PRESSED) {
                                    if (it.code == KeyCode.ESCAPE)
                                        closeModal()
                                }
                            }

                            stylesheets.addAll(FX.stylesheets)
                            icons += FX.primaryStage.icons
                            scene = this
                            [email protected]["tornadofx.scene"] = this
                        }
                    }

                    if (block) {
                        if (FX.reloadStylesheetsOnFocus || FX.reloadStylesheetsOnFocus) {
                            thread(true) {
                                Thread.sleep(5000)
                                configureReloading()
                            }
                        }
                        showAndWait()
                    } else {
                        show()
                        configureReloading()
                    }
                }
            }
        }
    }

    private fun Stage.configureReloading() {
        if (FX.reloadStylesheetsOnFocus) reloadStylesheetsOnFocus()
        if (FX.reloadViewsOnFocus) reloadViewsOnFocus()
    }

    fun closeModal() = modalStage?.apply {
        close()
        modalStage = null
    }

    val titleProperty = SimpleStringProperty()
    var title: String
        get() = titleProperty.get()
        set(value) = titleProperty.set(value)

    fun  fxml(location: String? = null): ReadOnlyProperty = object : ReadOnlyProperty {
        val value: T

        init {
            val componentType = [email protected]
            val targetLocation = location ?: componentType.simpleName + ".fxml"
            val fxml = componentType.getResource(targetLocation) ?:
                    throw IllegalArgumentException("FXML not found for $componentType")

            fxmlLoader = FXMLLoader(fxml).apply {
                resources = [email protected]
                setController(this@UIComponent)
            }

            value = fxmlLoader!!.load()
        }

        override fun getValue(thisRef: UIComponent, property: KProperty<*>) = value
    }


    inline fun  fxid() = object : ReadOnlyProperty {
        var value: T? = null

        override fun getValue(thisRef: UIComponent, property: KProperty<*>): T {
            return value ?: thisRef.fxmlLoader!!.namespace[property.name] as T
        }
    }

}

abstract class Fragment : UIComponent()

abstract class View : UIComponent(), Injectable

class ResourceLookup(val component: Component) {
    operator fun get(resource: String): String? = component.javaClass.getResource(resource)?.toExternalForm()
    fun url(resource: String): URL? = component.javaClass.getResource(resource)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy