Class DatePicker

java.lang.Object
limn.scene.Widget<DatePicker>
limn.components.date.DatePicker

public final class DatePicker extends Widget<DatePicker>
A date field with a calendar to pick from: the field is a real child, the calendar is a CalendarView in a popup, and this class is the wiring between them.

 new DatePicker()                                   // a date, with a calendar
 new DatePicker().setGranularity(MINUTE)            // a date and a time; the popup gains a time row
 new DatePicker().setGranularity(MONTH)             // a month picker
 DatePicker.ofRange()                               // a period: two fields, one calendar
 DatePicker.ofRange().setGranularity(MONTH)         // a period of whole months
 

Composition rather than modes. The typing, the segments and the validity are DateField's and are not reimplemented here; the grid, the bounds and the marks are CalendarView's. What this class owns is exactly three things: the trailing button, when the popup is open, and keeping the two halves in step.

Because the field is a widget rather than a painted region, a period is two fields with the caret moving between them on Tab, the clipboard works, and a screen reader walks a subtree that was already described. The picker paints one box around them so it reads as one control.

The popup takes no focus in a window of its own, which is the toolkit's popup contract and not this widget's choice: the field keeps the keyboard, and the navigation keys are forwarded to the grid while it is open, so Up, Down, PageUp, PageDown and Enter drive the calendar and the digits still reach the segments. In the in-scene presentation the overlay holds the focus instead and forwards every key and every character the same way, so the two presentations type alike. At an hour granularity or finer the popup carries a time row under the grid, and Tab walks the card from top to bottom, the header, the grid and that row. A pointer click in the popup's window makes it the desktop's key window; the keys it then receives are handed on exactly as the field's would be, and closing the popup gives the owner window the keyboard back.

The presentation follows DisplayMode: a window of its own where the platform can place one, an overlay inside the owner window where it cannot or where the owner is fullscreen, for the reasons that type gives — and an application that documents itself with screenshots asks for DisplayMode.IN_SCENE on purpose, because a native popup is absent from a capture of the window. The presentation is chosen when the popup opens; one already open in a window of its own when the owner enters or leaves fullscreen closes instead, as if dismissed, and what the fields hold is left as it was.

Like every widget, it is touched from the UI thread only: each method that changes it checks, and throws on any other thread.

  • Constructor Details

    • DatePicker

      public DatePicker()
      A picker for a date.
  • Method Details

    • setGranularity

      public DatePicker setGranularity(DateField.Granularity level)
      How fine this picker goes, on its field (or both ends of a period) and on its calendar at once. DateField.Granularity.MONTH makes a month picker: the field shows a month and a year and the popup opens on the twelve months, where a pick is the selection. DateField.Granularity.YEAR likewise. DateField.Granularity.HOUR and finer keep the day grid and add a time row under it.

      Set here and not on field(): the calendar has to follow, and a field told alone would leave a month picker opening on days it cannot pick.

      Parameters:
      level - how fine to go
      Returns:
      this
    • granularity

      public DateField.Granularity granularity()
      Returns:
      how fine this picker goes; DateField.Granularity.DAY unless it was changed
    • ofRange

      public static DatePicker ofRange()
      A picker for a period: two fields with the calendar between them in range mode. Focus in the first field anchors the grid on the start and focus in the second on the end, and the grid writes back into whichever end is being filled.

      The period reads as one line packed at the start of the box, as it would be typed: the first date, an en dash a small gap after it, the second date a small gap after the dash, and whatever room the box has left after the second date, before the calendar button. The dash follows the first date as it fills.

      Returns:
      the picker
    • field

      public DateField field()
      Returns:
      the field this picker types into; the first of the two for a period
    • endField

      public @Nullable DateField endField()
      Returns:
      the second field of a period, or null for a picker that is not one
    • calendar

      public CalendarView calendar()
      Returns:
      the grid this picker opens. Reach through it for anything the picker does not repeat: CalendarView.setShowWeekNumbers(boolean), CalendarView.setDayMarks(java.util.function.Function<java.time.LocalDate, limn.components.date.DayMark>), CalendarView.setFirstDayOfWeek(java.time.DayOfWeek)
    • date

      public @Nullable LocalDate date()
      Returns:
      the date, or null while the field is incomplete
    • time

      public @Nullable LocalTime time()
      Returns:
      the time of day, or null unless this picker carries one and it is complete
    • dateTime

      public @Nullable LocalDateTime dateTime()
      Returns:
      date and time together, or null unless both are complete
    • range

      public @Nullable DateRange range()
      Returns:
      the period, or null unless this is a range picker with both ends filled
    • setDate

      public DatePicker setDate(@Nullable LocalDate date)
      Sets the date. Reaches the field, which announces it, which is what this picker forwards.
      Parameters:
      date - the date, or null to clear
      Returns:
      this
    • setDateTime

      public DatePicker setDateTime(@Nullable LocalDateTime value)
      Sets date and time at once.
      Parameters:
      value - the moment, or null to clear
      Returns:
      this
    • setRange

      public DatePicker setRange(@Nullable DateRange range)
      Sets the period, filling both fields.
      Parameters:
      range - the period, or null to clear both ends
      Returns:
      this
      Throws:
      IllegalStateException - if this picker is not a range picker
    • setMinDate

      public DatePicker setMinDate(@Nullable LocalDate date)
      The earliest acceptable date, on the field and the grid: the two enforce it at different moments (the grid refuses a click, the field holds a typed date and says it is invalid), and setting it in one place is what keeps them from disagreeing.
      Parameters:
      date - the bound, or null for none
      Returns:
      this
    • setMaxDate

      public DatePicker setMaxDate(@Nullable LocalDate date)
      The latest acceptable date, on the field and the grid alike.
      Parameters:
      date - the bound, or null for none
      Returns:
      this
    • setDateFilter

      public DatePicker setDateFilter(@Nullable Predicate<LocalDate> filter)
      Which dates are acceptable, on the field and the grid alike. The grid calls it once per painted cell, so the obligation CalendarView.setDateFilter(java.util.function.Predicate<java.time.LocalDate>) states — cheap, and pure — is the one that binds here.
      Parameters:
      filter - the filter, or null to accept every date
      Returns:
      this
    • setChronology

      public DatePicker setChronology(@Nullable Chronology chronology)
      The calendar system the field and the grid are drawn in; the value stays ISO.
      Parameters:
      chronology - the calendar, or null to follow the language
      Returns:
      this
    • setClock

      public DatePicker setClock(@Nullable Clock clock)
      Where the picker's parts read today from: the calendar's ring and opening month, and the fields' first step on an empty segment. See CalendarView.setClock(java.time.Clock).
      Parameters:
      clock - the clock, or null for the system's
      Returns:
      this
    • isOpen

      public boolean isOpen()
      Returns:
      whether the calendar is showing
    • setDisplayMode

      public DatePicker setDisplayMode(DisplayMode mode)
      Asks for a presentation of the calendar; see DisplayMode. Default DisplayMode.NATIVE_WINDOW, so the grid can extend past the window's edge the way a platform picker's does.

      A preference, not a guarantee. A calendar asked for NATIVE_WINDOW is still drawn in scene on a window that cannot be positioned at an anchor (Wayland), and over a fullscreen window, for the reasons DisplayMode gives. IN_SCENE is always honoured. Read displayMode() for what happened. Takes effect on the next open.

      Parameters:
      mode - what to ask for
      Returns:
      this
    • displayMode

      public DisplayMode displayMode()
      Returns:
      how the calendar is presented: while it is open, what it actually chose, including a fullscreen owner or a platform forcing IN_SCENE over a request for a window. While it is closed, what was last asked for, since the next open decides afresh over the window as it is then.
    • open

      public DatePicker open()
      Opens the calendar. Does nothing if it is already open or the picker is disabled, nor, in a scene, while the picker is hidden, drawn as nothing by a transform above it, or on its way out of its window.
      Returns:
      this
    • close

      public DatePicker close()
      Closes the calendar.
    • onSelect

      public DatePicker onSelect(@Nullable Runnable listener)
      The application's response to the user choosing: a date typed or picked from the grid, the end of a period, or a time of day. The handler reads what it needs, date(), range() or dateTime().
      Parameters:
      listener - what to run, or null to clear the slot
      Returns:
      this
      Throws:
      IllegalStateException - if a handler is already registered
    • handleUserChange

      protected void handleUserChange(Change.Aspect aspect)
      Description copied from class: Widget
      Runs this widget's application handler for a change it has just announced. Called by Widget.runHandler(limn.scene.Change), and so by Widget.notifyChange(limn.scene.Change), only for a USER origin and after every watcher has run.

      A component overrides it once and switches on the aspect, reading its own accessors for the payload: a slider passes value(), a combo box selectedIndex(), a text field text(). A component with no handler does not override it.

      An override that does not recognise the aspect must call super, because a subclass of a component that has a handler inherits that component's dispatch through this method: a SearchField that overrode this for its own aspect and did not chain would silently delete TextField's. The ordering lives in the base class, this is the one link in it a subclass can break, and the contract test drives every subclass through its parent's gestures to catch one that does.

      Overrides:
      handleUserChange in class Widget<DatePicker>
      Parameters:
      aspect - what changed; the origin is USER or this is not called
    • onDetached

      protected void onDetached()
      Description copied from class: Widget
      Called when this widget leaves the scene: detached from the tree, moved into another scene (told this before the Widget.onAttached() of the scene it joins), or still in it when the scene's window closes, which detaches every widget the scene has. What was taken in Widget.onAttached() from something that outlives the window (a listener's place in a list of the process's, a resource of the system's) is given back here.

      Widget.scene() still answers the scene being left, and becomes null once this returns, so releasing something the scene owns needs no field of your own to remember it by. Fires bottom-up, so children run before their parent, and every one of them can still reach the scene.

      Release resources here, but GPU resources must be handed to Scene.disposeLater(limn.graphics.GpuSurface) (disposal needs the owning GL context, which is only current inside a frame). Not called on Widget.setVisible(boolean). Default no-op.

      Overrides:
      onDetached in class Widget<DatePicker>
    • onMeasure

      protected Size onMeasure(Constraints constraints)
      Description copied from class: Widget
      Reports the size this widget wants within constraints. Called once per layout pass, and the result is cached against the constraints and the resolved axes (size step, layout direction, locale), so it must be a pure function of them and of this widget's own state.

      Resolve the ControlSize and the LayoutDirection once each here and thread them down; never read either in a constructor. The locale needs no threading: it is in scope, and I18n.locale() answers it wherever text is resolved.

      Specified by:
      onMeasure in class Widget<DatePicker>
    • onLayout

      protected void onLayout()
      Description copied from class: Widget
      Containers position children here (measure + Widget.layoutBox(float, float, float, float) per child).
      Overrides:
      onLayout in class Widget<DatePicker>
    • paintOutset

      protected float paintOutset()
      Description copied from class: Widget
      How far beyond this widget's bounds its painting may reach, in logical points: its own painting and any descendant no ancestor clips (a drop shadow, a glow, a child positioned outside the parent's box). Partial rendering uses it both ways: Widget.invalidate() damage grows by it, and a repaint pass skips this subtree only when bounds + outset miss the pass region; painting farther than declared leaves stale pixels.

      The work is divided by who knows the number. A widget declares what it paints itself outside its box: Label's icon overhang, the focus ring Button, Checkbox and RadioButton draw outside theirs. A widget that lays out children and does not clip them declares how far they may paint past it, because a ring on a child at its edge hangs past it too, and a pass landing only in that band would otherwise skip it and never reach the child. Every layout the toolkit ships does that by default with the reach the theme gives a focus ring, and takes another number through its public paintOverflow, which sets what this hook answers; a layout an application writes inherits both by extending limn.scene.layout.Container, and one that extends this class directly overrides this hook itself. A widget that clips its children declares only its own painting, since the clip bounds theirs.

      Asked of every widget a repaint pass considers, and on every invalidate(), so an override answers in constant time without allocating: a number held or looked up, never a walk over the children. Default 0: everything stays inside the bounds, which is true for most leaves.

      Overrides:
      paintOutset in class Widget<DatePicker>
    • onPaint

      protected void onPaint(Canvas canvas)
      Description copied from class: Widget
      Widget's own background/content, in local coordinates.
      Overrides:
      onPaint in class Widget<DatePicker>
    • paintsDecoration

      protected boolean paintsDecoration()
      The box, the focus ring and a period's dash are the chrome around the fields and the button, which say everything a reader needs. A single picker is no node, so the walk's paints-and-says-nothing guard named this class in every application's log and recommended setAccessibleName — the one call that would make the group a node again and leave the field nameless — or setAccessibleIgnored, which would take the field and the button out with it. The TabbedPane and BackdropPanel case; a range picker keeps its group and is never asked.
      Overrides:
      paintsDecoration in class Widget<DatePicker>
      Returns:
      true; what this widget draws frames its children
    • onAccessibility

      protected void onAccessibility(Accessibility a)
      What the composite says of itself: a single picker says nothing and is no node — its field is the picker a reader meets, carries the label bound to the picker, the popup state and the verbs that open and close it — and a range picker is a group that keeps the label over its two named ends. The expanded state this group once carried, and the verbs it accepted without publishing, are the field's now; the button is a plain press. The fields and the button are real children and describe themselves, which is the whole reason they are widgets.
      Overrides:
      onAccessibility in class Widget<DatePicker>
      Parameters:
      a - the node being described; write into it, and never keep it
    • accessibleLabelTarget

      protected Widget<?> accessibleLabelTarget()
      Where a caption bound to this picker lands: on the single field, which is the node a reader arrives at; a range picker keeps it on the group, whose two ends are named "Start date" and "End date" for themselves.
      Overrides:
      accessibleLabelTarget in class Widget<DatePicker>
      Returns:
      the widget that carries a label bound to this one; this by default
      See Also: