Class ButtonGroup

java.lang.Object
limn.components.ButtonGroup

public final class ButtonGroup extends Object
Coordinates a set of RadioButtons so that exactly one is selected at a time. Selecting a member deselects the previous one; when the user did the selecting each radio's own onChange fires for both the leaving and the entering member and then the group's onSelect fires with the new index, and when code did it none of the three handlers runs -- the two members announce their VALUE to their watchers either way.

A group is not a widget, so it announces nothing of its own. It has no box, no parent and no scene, so there is no node a watcher could attach its change to; the change reaches the channel entire through the two members, which are widgets: the leaver's VALUE and then the enterer's, both carrying the origin of whatever entered the swap, and only then either member's handler. The whole swap settles before the first announcement, because a half-swapped group is unreadable.

A group is one tab stop, not one per radio. Only the selected member (or the first enabled one, before anything is selected) can take focus, and the arrow keys move the selection and the focus together, wrapping at the ends and stepping over disabled members. That is the Windows and GTK convention, it is what macOS Full Keyboard Access does inside a group, and it is the pattern TabbedPane already uses for its headers. Without it a settings form of six groups of four options costs twenty-four tab stops instead of six.

  • Constructor Details

    • ButtonGroup

      public ButtonGroup()
  • Method Details

    • add

      public ButtonGroup add(RadioButton radio)
      Adds a radio to the group. If it is already selected, it becomes the group's selection.

      Membership is what a screen reader speaks as "2 of 4", and adding a member changes that number for every member while painting nothing: the roving-focus pass only invalidates the tree when a focusable flag actually flips, and the first member's does not flip at all. So every member tells the tree itself, which costs nothing for a member not yet in a scene and is not per-frame work.

    • remove

      public ButtonGroup remove(RadioButton radio)
      Takes a radio out of the group for good: it is a radio on its own from then on. If it was the group's selection the group has none, and the radio stays selected. Nothing happens for a radio that is not a member. UI thread only.

      A radio leaves by itself, too, for as long as it is out of a scene it was in: taken out of the tree, or its window closed. The group does not hold it then, the others no longer count it in the "2 of 4" a screen reader speaks, and it takes its place again, among the members that are there, when it is back in a scene. A radio added to a group before it was ever in a scene is a member from the start.

      Parameters:
      radio - the radio to take out
      Returns:
      this group
    • onSelect

      public ButtonGroup onSelect(@Nullable IntConsumer listener)
      The application's response to the user choosing a member: called with its index after a click, an arrow key or an assistive technology's select, once both members' own handlers have run. Never for setSelectedIndex(int), clearSelection() or a member's RadioButton.select(), which are a caller's writes; to hear every change whatever caused it, watch the members.
      Parameters:
      listener - the handler, or null to clear the slot
      Returns:
      this group
      Throws:
      IllegalStateException - if a handler is already registered
    • selectedIndex

      public int selectedIndex()
      Returns:
      the index of the selected radio, or -1 when none. A group is one of the two widgets in this set that genuinely has no-selection as a state: it starts there, and clearSelection() goes back
    • selected

      public @Nullable RadioButton selected()
      Returns:
      the selected radio, or null when none.
    • members

      public List<RadioButton> members()
      Returns:
      an immutable view of the members, in the order they were added; one that is out of the scene it was in is not among them until it is back
    • setSelectedIndex

      public ButtonGroup setSelectedIndex(int index)
      Selects the member at index: a caller's write, so both members announce VALUE/CODE and no handler runs. Selecting the member that is already selected changes nothing and announces nothing. UI thread only.
      Parameters:
      index - a member in [0, memberCount), in the order they were added
      Throws:
      IndexOutOfBoundsException - if index is not a member; a group with no members has none, so every index throws there. -1 is not the way to empty the group; clearSelection() is.
    • clearSelection

      public ButtonGroup clearSelection()
      Puts the group back in the state it had before anything was selected: selectedIndex() reports -1 and the leaving member announces VALUE/CODE; no handler runs, because a caller emptied the group. No-op when nothing is selected. UI thread only.

      A radio group is one choice out of many and offers no way to un-choose from the keyboard or the mouse: this is the reset a form needs, and the only route back.