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.7
Show newest version
package tornadofx

import javafx.beans.property.SimpleObjectProperty
import javafx.beans.property.SimpleStringProperty
import javafx.collections.FXCollections
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.Clipboard
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 java.util.prefs.Preferences
import kotlin.concurrent.thread
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

interface Injectable

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

    val clipboard: Clipboard by lazy { Clipboard.getSystemClipboard() }

    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) }
        }
    }

    /**
     * Store and retrieve preferences.
     *
     * Preferences are stored automatically in a OS specific way.
     * 
    *
  • Windows stores it in the registry at HKEY_CURRENT_USER/Software/JavaSoft/....
  • *
  • Mac OS stores it at ~/Library/Preferences/com.apple.java.util.prefs.plist
  • *
  • Linux stores it at ~/.java
  • *
*/ fun preferences(nodename: String? = null, op: Preferences.() -> Unit) { val node = if (nodename != null) Preferences.userRoot().node(nodename) else Preferences.userNodeForPackage(FX.application.javaClass) op(node) } val properties by lazy { FXCollections.observableHashMap() } val log by lazy { Logger.getLogger([email protected]) } 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 { override fun getValue(thisRef: UIComponent, property: KProperty<*>): T { val value = thisRef.fxmlLoader!!.namespace[property.name] if (value is T) return value if (value == null) log.warning("Property ${property.name} of $thisRef was not resolved because there is no matching fx:id in ${thisRef.fxmlLoader!!.location}") else log.warning("Property ${property.name} of $thisRef did not resolve to the correct type. Check declaration in ${thisRef.fxmlLoader!!.location}") throw IllegalArgumentException("Property ${property.name} does not match fx:id declaration") } } } 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