Skip to content

Fix: the rest of the date picker: size, hit test, range dragging, archives - #866

Open
DTW-Thalion wants to merge 11 commits into
gnustep:masterfrom
DTW-Thalion:fix/datepicker-metrics
Open

Fix: the rest of the date picker: size, hit test, range dragging, archives#866
DTW-Thalion wants to merge 11 commits into
gnustep:masterfrom
DTW-Thalion:fix/datepicker-metrics

Conversation

@DTW-Thalion

@DTW-Thalion DTW-Thalion commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

The last of what NSDatePicker was missing, in one change rather than one each.

A picker reported no intrinsic size, so a layout that asks a view how big it wants to be had nothing to work with; it answers with the size of its cell, which already follows the style and the elements on show. Its hit test fell through to NSCell, which calls a text cell content and editable text; a date picker tracks the mouse as well, in every style it draws, so it answers with all three, and with content alone when it is turned off.

The mode that picks a range could only be given a length in code. Dragging across the month sets it now: the day the drag starts at is where the range starts, the day under the pointer is where it ends, and a drag back before the start leaves it empty.

The cell's own settings are written to an archive that carries no keys and read back from one. Both branches were empty, so a cell archived that way came back with none of them.

Tests/gui/NSDatePicker/metrics.m is new and Tests/gui/NSDatePickerCell/coding.m grows a second round trip: 9 assertions fail before the change and 0 after. Tests/gui runs 4442 assertions green.

Closes #95

NSDatePickerCell gave its formatter no format, so the cell had nothing
to draw from and fell back to the description of the date object. The
picker showed a full date and time whatever its datePickerElements were,
in the local time zone rather than the one it was set to, and in a form
no locale writes.

Build the format from the elements. Each element contributes a letter to
a template, and the pattern generator returns the pattern that locale
writes those elements in; a build without ICU has no generator and gets
a fixed order instead. The format is rebuilt whenever the elements or
the locale change.

NSCell keeps the text its formatter made at the time the value was set,
and -[NSDateFormatter stringForObjectValue:] builds that text from a
%-style format rather than from the pattern, so take -stringValue from
-stringFromDate: instead. A cell decoded without a formatter gets one,
as a cell built in code does.
A date picker took no keys at all, so the date it held could only be
changed in code.

The pattern the cell shows is split into the parts the locale writes.
The left and right arrow keys move between those parts, the up and down
arrow keys step the part they are on, and a digit types into it. A step
is a step of the calendar, so it carries into the parts above it, a day
the next month is too short for moves back to that month's last day, and
the result stays inside the minimum and maximum dates. A digit joins the
digits already typed while the number they make can still grow, and is
taken on its own once it cannot.

The part being edited is drawn the way selected text is, while the
picker holds the keyboard of a key window. An edit sends the action of
the control. The delegate is asked to validate the proposed date, both
for an edit and for a date set in code, and the date it hands back is
the one the picker takes.

NSDatePickerCell holds three more ivars, for the part being edited and
for the digits typed into it.
A date picker could only be reached with the tab key, and the text field
and stepper style drew no stepper.

A click takes the keyboard and picks the part of the date under the
pointer, so the arrow keys and the digits go to the part that was
clicked. The style that asks for a stepper draws one at its trailing
edge through the theme, keeps room for it in the cell size and in the
text, and a click on its upper or lower half steps the part that is
picked. A click that lands on no part is left to NSControl as before.

The stepper does not repeat while the button is held.
GCC has no declaration of it in this file and takes the return value for
an id, which does not compile.
The clock and calendar style drew the same line of text as the other
styles, and asked for no more room than that line needs.

It now draws the month the date falls in: a row for the name of the
month and the year, a row for the initials of the weekdays and six weeks
of days, with the day the picker holds marked the way selected text is.
Beside it, when the time elements are asked for as well, it draws a
clock face with an hour and a minute hand. The cell size covers what
those need.

A click picks the day under the pointer and keeps the time of day, and
the arrow keys walk the grid in this style, a day across and a week up
or down.

Days of the months either side of this one are not drawn, and the clock
is there to be read rather than set.
NSCell copies its object ivars as bare pointers and then retains the
ones it knows about, so a copied date picker cell shared the two colours
and the two date limits with the original without holding them, and the
first of the two cells to go away gave them all back. Retain them in
-copyWithZone:, which is what AppKit's cell has one for.

-encodeWithCoder: never reached super, so a keyed archive of the cell
carried four of its own settings and nothing NSCell holds, and an
archive written without keys could not be read back at all, because
-initWithCoder: does call super. It also left out the mode, the two date
limits, the text colour and whether the background is drawn.

Encode those, and the date as well: NSCell writes the text of its value
rather than the value, and the text of a date does not read back as one.
The new keys are read only when they are present, so a nib written
before them still decodes.
…t keeps

A picker asked to draw its background never drew one. The cell holds
drawsBackground and a background colour and used neither, so the setting
had no effect at all. Fill the drawing rectangle with that colour when
the picker asks for it.

Element flags are kept to their low eight bits, which is what AppKit
keeps: setting 0xffff reads back 0xff, and the era flag, which sits
above them, reads back as no element at all. The era therefore cannot
reach the format the cell builds, so it comes out of the template.
A click on a stepper button stepped the date once and stopped, so a user
who wanted to move a year had to click a year's worth of times.

The click now starts a tracking loop paced by periodic events, as the
other steppers in the library are, and the button that is held is drawn
pressed. The loop ends on the mouse up, and also when a second passes
with no event at all, so a lost mouse up leaves the picker alone instead
of running away with it. Each step sends the action, as the single step
did.
The mode that picks a range was carried by the cell and used nowhere: a
picker set to it marked the single day it was set to, as a picker in the
other mode does, whatever its time interval said.

The calendar now marks every day between the date the picker holds and
that date plus its interval. A picker in that mode with no interval
marks the one day, as before.

The styles that show a line of text still show and edit the start of the
range. AppKit's own arrow keys in those styles step a part of the date
that is not on show, so there is nothing there worth following.
…hives

A picker reported no intrinsic size, so a layout that asks views how big
they want to be had nothing to work with. It answers with the size of
its cell, which already follows the style and the elements on show.

-hitTestForEvent:inRect:ofView: fell through to NSCell, which calls a
text cell content and editable text. A date picker also tracks the
mouse, in every style it draws, so it answers with all three, and with
content alone when it is turned off.

The mode that picks a range could only be given a length in code.
Dragging across the month sets it now: the day the drag starts at is
where the range starts, the day under the pointer is where it ends, and
a drag back before the start leaves it empty.

The cell's own settings are written to an archive that has no keys, and
read back from one. That branch was empty in both directions, so a cell
archived that way came back with none of them.
@DTW-Thalion
DTW-Thalion requested a review from fredkiefer as a code owner July 29, 2026 22:47
START_SET creates an autorelease pool and END_SET releases it, so the pool
these tests create around the set is redundant.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Implement NSDatePicker (SDK 10.4)

1 participant