Class AbstractSpinnerDataBinding<V>

java.lang.Object
com.amalgamasimulation.desktop.binding.widgets.AbstractSpinnerDataBinding<V>
Type Parameters:
V - The type of the model value being bound
All Implemented Interfaces:
AbstractDataBinding<V>
Direct Known Subclasses:
ContainerSpinnerDataBinding, SpinnerDataBinding

public abstract class AbstractSpinnerDataBinding<V> extends Object implements AbstractDataBinding<V>
Base binding between an SWT Spinner and a model value of type V. The converter turns the value into a number, and the spinner shows it with digits places after the point. The arrows and the Up, Down, Page Up and Page Down keys send the value to the model at once; typed text is sent on Enter or when the spinner loses focus. If the typed text is not a number, the number is out of range or the value fails the validation, an error icon is shown next to the spinner, and after Enter or focus loss the spinner shows the model value again.

Spinner keeps an integer (the selection) and the number of digits after the point, so the value is multiplied by 10 to the power of digits and rounded. Integer, Double and other types differ only in the converter, so there are no separate classes for them.

What the SWT Spinner does on Windows

(SWT 3.134, Spinner.java) It is made of two Windows controls: a text field and the arrow buttons. The selection is kept in the buttons.
  • The text changes (EN_CHANGE in wmCommandChild). If the text is a number in the range and differs from the selection, the spinner updates the selection and puts Selection in the queue. Then it sends Modify at once. If the text is not a number or the number is out of range, the selection stays the same and Selection is not sent, but Modify is sent anyway.
  • The Up, Down, Page Up and Page Down keys (wmKeyDown). KeyDown comes first. Then the spinner takes the number from the text, or the previous selection if the text is not valid, adds the step, clips it to the range and calls setSelection with notify. That call writes the text, which brings Modify, and puts Selection in the queue.
  • A mouse click on an arrow (UDN_DELTAPOS in wmNotifyChild). The same, without KeyDown.
  • Enter (wmChar). Puts DefaultSelection in the queue; the text does not change.
  • Focus loss (wmKillFocus). If the text is not valid, the spinner puts the text of the previous selection back. This brings Modify without Selection. Then FocusOut comes.
  • setSelection called from code. Writes the text; Modify comes, Selection does not.
Selection is sent by sendSelectionEvent, which puts the event in the queue (Widget.sendEvent with send = false), so it always comes after Modify.

The listeners of the binding (createBinding)

  1. KeyDown sets isKeyPressed for any key except Up, Down, Page Up and Page Down.
  2. Modify stores in isTypedModify whether a key was pressed. If it was, this is typing: isKeyPressed is reset, isTyping is set, the text is parsed into typedSelection (parseSelection), and the error icon is shown or hidden.
  3. Selection calls executeToModel only if this is not typing (neither isKeyPressed nor isTypedModify), and resets isTyping before that. Selection from typing is skipped because the spinner sends it for every valid intermediate number: typing "25" would send "2" to the model. isKeyPressed alone is not enough for the check: when Selection comes from the queue, Modify has already reset it.
  4. DefaultSelection (Enter) and FocusOut call executeToModel.

executeToModel

  • takes typedSelection if the user was typing, otherwise the selection of the spinner. The selection of the spinner cannot be used after typing, because with invalid text the spinner keeps the last valid one. With the range from 0 to 10, after "1" and "11" getSelection() returns 1, and Enter would write 1;
  • resets the flags;
  • writes to the model if the number is not null, differs from shownSelection and passes the validation;
  • always calls executeToTarget at the end, which shows the model value again and stores shownSelection.

Selections, not values, are compared with shownSelection, because the spinner shows the value rounded to digits places. If the model holds 1.234 and digits is 2, the spinner shows 1.23, and comparing values would write 1.23 when the user just enters the field and leaves it. For the same reason focus loss writes nothing when the model holds null (the spinner shows the minimum) or a value out of range (the spinner shows the nearest bound).

parseSelection parses the text by the rules of the spinner. The decimal separator is taken from the text of the spinner, because the spinner shows the separator from the Windows settings, and it may differ from the Java locale. Extra digits after the separator are cut off the same way the spinner cuts them.

canBeNull only hides the icon for null. The icon is not shown while the spinner is disabled.

Author:
Ionov Ivan
See Also:
  • Field Details

    • spinner

      protected org.eclipse.swt.widgets.Spinner spinner
      The spinner the value is bound to. Set by setControl.
    • toNumber

      protected ToDoubleFunction<V> toNumber
      Turns a model value into a number for the spinner.
    • fromNumber

      protected DoubleFunction<V> fromNumber
      Turns a number of the spinner back into a model value.
    • minimum

      protected V minimum
      The lower bound of the value. createBinding makes it the minimum of the spinner.
    • maximum

      protected V maximum
      The upper bound of the value. createBinding makes it the maximum of the spinner.
    • digits

      protected int digits
      The number of digits after the point.
    • validation

      protected Predicate<V> validation
      Check of the value. A value that fails it is not written to the model. If the model value fails it, the error icon is shown. By default the check accepts any value.
    • validationMessage

      protected String validationMessage
      Tooltip text of the error icon. The tooltip is set once in createBinding, so calling validation after it does not change the text.
    • canBeNull

      protected BooleanSupplier canBeNull
      Whether the model may hold null. If it may, the error icon is not shown for null. The function is called every time the binding shows null from the model. By default null is not allowed.
    • controlDecoration

      protected org.eclipse.jface.fieldassist.ControlDecoration controlDecoration
      The error icon of the spinner. createBinding creates it hidden.
  • Constructor Details

    • AbstractSpinnerDataBinding

      protected AbstractSpinnerDataBinding()
  • Method Details

    • executeToTarget

      public void executeToTarget()
      Shows the model value in the spinner. null is shown as the minimum of the spinner. The spinner clips a value out of range itself, so shownSelection is read from the spinner after setSelection instead of being taken from toSelection. If digits is above zero, the decimal separator is read from the text of the spinner: it stands before the last digits characters. It is read here because the spinner takes the separator from the Windows settings, and the shown text is the only way to learn it. At the end the error icon is updated. Called at the end of createBinding and of every executeToModel, when the spinner is enabled (enable) and when the section is updated.
      Specified by:
      executeToTarget in interface AbstractDataBinding<V>
    • executeToModel

      public void executeToModel()
      Sends the number the user set in the spinner to the model and shows the model value again. The typing flags are reset before the write because executeToTarget calls setSelection, and its Modify must not count as typing. After Enter, isKeyPressed is still set by the Enter key itself. The number is not written if it is null (the text is not a number or the number is out of range), if it equals shownSelection, or if the value fails the validation. executeToTarget is always called, even if nothing was written: it puts the model value back in place of invalid text and updates the icon. Where the number comes from and why is described in the class Javadoc.
      Specified by:
      executeToModel in interface AbstractDataBinding<V>
    • createBinding

      public void createBinding()
      Creates the error icon, sets the range and digits of the spinner, subscribes to its events and shows the model value. The arrow step is set to 1 and the page step to 10 in selection units; the section may replace them with its own after createBinding. Throws IllegalArgumentException if digits is below 0 or the minimum is greater than the maximum, because the spinner would ignore such values without an error. The tooltip of the icon is set here once, so validation must be called before createBinding. The listeners and the order of their events are described in the class Javadoc.
      Specified by:
      createBinding in interface AbstractDataBinding<V>
    • toSelection

      public int toSelection(V value)
      Converts a value into the spinner selection, taking the digits into account.
      Parameters:
      value - the value, not null
      Returns:
      the selection of the spinner
    • enable

      public void enable(boolean enable)
      Description copied from interface: AbstractDataBinding
      Enables or disables the bound control.
      Specified by:
      enable in interface AbstractDataBinding<V>
      Parameters:
      enable - true to enable, false to disable
    • setControl

      public void setControl(Object control)
      Description copied from interface: AbstractDataBinding
      Sets the UI control for this binding.
      Specified by:
      setControl in interface AbstractDataBinding<V>
      Parameters:
      control - The control to bind to
    • converter

      public AbstractSpinnerDataBinding<V> converter(ToDoubleFunction<V> toNumber, DoubleFunction<V> fromNumber)
    • range

      public AbstractSpinnerDataBinding<V> range(V minimum, V maximum)
    • digits

      public AbstractSpinnerDataBinding<V> digits(int digits)
    • validation

      public AbstractSpinnerDataBinding<V> validation(Predicate<V> validation, String message)
    • canBeNull

      public AbstractSpinnerDataBinding<V> canBeNull(BooleanSupplier canBeNull)