From bce5a0d39e599e7ba6512af06ccef4ec798f9492 Mon Sep 17 00:00:00 2001 From: Sarah Gerweck Date: Sun, 5 Nov 2017 21:40:44 -0800 Subject: [PATCH] New `ObservablePref` mechanism This inherits from the `Pref` system in `scala-utils`, extending it so that preferences can be ScalaFX bound variables. This is the easiest way to store things like the preferred window size and other per-user local configuration. --- CHANGELOG.md | 9 ++++ project/Dependencies.scala | 2 +- .../scalafx/util/prefs/ObservablePref.scala | 54 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/org/gerweck/scalafx/util/prefs/ObservablePref.scala diff --git a/CHANGELOG.md b/CHANGELOG.md index b57344a..487ac70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,3 +68,12 @@ change. * This is tested against Java 8u144, though it may work with older versions. * Improvements to `SingletonStage` * Update Scala to 2.12.4 + +### 0.13 + +* New `ObservablePref` + * This builds on the `Pref` in `scala-utils`, making it a bindable property. + * `Pref` and `ObservablePref` are the way I recommend to store things like + window sizes, column selections and other UI preferences. + * Use a database like [H2](http://www.h2database.com/) if you have + complicated application state that needs to persist. diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 2f18520..3216463 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -23,7 +23,7 @@ object Dependencies { final val scalaJava8Version = "0.8.0" final val scalaParserVersion = "1.0.4" final val scalaXmlVersion = "1.0.5" - final val gerweckUtilVersion = "2.5.1" + final val gerweckUtilVersion = "2.7.0-SNAPSHOT" final val scalazVersion = "7.2.16" final val shapelessVersion = "2.3.2" final val scallopVersion = "1.0.1" diff --git a/src/main/scala/org/gerweck/scalafx/util/prefs/ObservablePref.scala b/src/main/scala/org/gerweck/scalafx/util/prefs/ObservablePref.scala new file mode 100644 index 0000000..167517b --- /dev/null +++ b/src/main/scala/org/gerweck/scalafx/util/prefs/ObservablePref.scala @@ -0,0 +1,54 @@ +package org.gerweck.scalafx.util +package prefs + +import java.util.prefs.Preferences + +import scalafx.application.Platform.runLater +import scalafx.beans.property.ObjectProperty + +import org.gerweck.scala.util.prefs._ + +/* TODO: take an implicit that will deteremine whether this is an `ObjectProperty` or what */ +class ObservablePref[A] protected (path: String)(implicit handler: Pref.AccessHandler[A], prefs: Preferences) + extends Pref[A](path) { + + lazy val observe: ObjectProperty[A] = { + val initialValue: A = this() + val property = ObjectProperty[A](initialValue) + + /* We build two bridges, one that listens for changes in the preferences system and pushes + * them into the observable property, and another that listens for updates to the property and + * pushes them to the preference system. Each bridge is gated so that it only activates if the + * value has actually changed, which prevents the infinite looping that would otherwise occur + * in a bidirectional bridge. */ + + /* Preferences => Property bridge */ + prefs.addPreferenceChangeListener { pce => + if (pce.getKey == path) { + runLater { + val currentValue = this() + if (property.value != currentValue) { + property.value = currentValue + } + } + } + } + /* Property => Preferences bridge */ + property.onChange { (_, _, newV) => + if (newV != this()) { + this() = newV + } + () + } + property + } +} + +object ObservablePref { + def apply[A](path: String)(implicit handler: PrefHandler[A], prefs: Preferences) = { + new ObservablePref(path)(new Pref.AccessHandler.Optional, prefs) + } + def apply[A](path: String, default: A)(implicit handler: PrefHandler[A], prefs: Preferences) = { + new ObservablePref(path)(new Pref.AccessHandler.Defaulted(default), prefs) + } +}