= Solution
Choose a deterministic editing policy: editing pounds or the exchange rate preserves the pounds amount and recalculates euros; editing euros preserves the rate and recalculates pounds. A valid rate must be finite and positive; amounts may be any finite signed number. Keep an unrounded numeric model, so repeatedly refreshing a two-decimal display does not feed rounded text back into the computation. Place the following members inside the supplied `MainFrame`, after its fields are created:
``
private var poundsValue = 10.0
private var eurosValue = 11.5
private var rateValue = 1.15
private var refreshing = false
private def finite(x: Double): Boolean =
!java.lang.Double.isNaN(x) && !java.lang.Double.isInfinite(x)
private def number(s: String): Option[Double] =
scala.util.Try(s.trim.toDouble).toOption.filter(finite)
private def money(x: Double): String =
java.lang.String.format(java.util.Locale.ROOT, "%.2f",
java.lang.Double.valueOf(x))
private def refresh(): Unit = {
refreshing = true
try {
pounds.text = money(poundsValue)
euros.text = money(eurosValue)
rate.text = money(rateValue)
} finally {
refreshing = false
}
}
private def commit(p: Double, e: Double, r: Double): Unit = {
if (finite(p) && finite(e) && finite(r) && r > 0.0) {
poundsValue = p
eurosValue = e
rateValue = r
refresh()
}
}
listenTo(pounds, euros, rate)
reactions += {
case EditDone(source) if !refreshing =>
number(source.text).foreach { value =>
if (source eq pounds)
commit(value, value * rateValue, rateValue)
else if (source eq euros)
commit(value / rateValue, value, rateValue)
else if ((source eq rate) && value > 0.0)
commit(poundsValue, poundsValue * value, value)
}
}
``
The mathematical model <invariant> is $eurosValue=poundsValue\,rateValue$ with a positive rate. In <floating-point arithmetic>, each update establishes the corresponding rounded multiplication or division; a division followed by multiplication need not recover bitwise identical values. Pounds and rate edits calculate a matching euro amount, while euro edits calculate its matching pound amount. An invalid parse, a nonpositive rate, or a nonfinite calculated result leaves the last valid numeric model unchanged. An invalid text entry may remain visible until corrected or a subsequent valid refresh; a production interface could add a validation message.
Every accepted edit refreshes the fields from that model. `Locale.ROOT` makes the decimal separator match the `toDouble` parser, independently of the machine's locale. The guard prevents a synchronous <reentrant event handler> from interpreting a refresh as another user commit. The displayed values are rounded to two decimal places, including the rate, while the stored values retain full `Double` precision. Very small positive rates can therefore display as `0.00`; using more rate digits is a reasonable presentation refinement. \b[Pounds, euros and rate edits all propagate by the stated policy], with no parsing exception or division by zero.
`EditDone` handles completion of an edit, such as an action or focus-change notification, using the event type supplied in the paper. It is not a promise of recalculation after every keystroke. A character-by-character interface would subscribe to the corresponding text-change event instead. The publisher/reactor interaction follows the supplied contract and official <Scala Swing> documentation; no exact course library build or native graphical execution is presumed.<Scala Swing event-system documentation>[Publisher/Reactor subscriptions and reactions; the paper supplies the EditDone event contract]{ref}
Back to article page