Files
Gabriel Brown d96863b687 Convert British spellings to American across the repo
colour -> color, behaviour -> behavior, centre -> center, favourite ->
favorite, and about twenty other pairs, applied consistently across
comments, docs, error/UI copy, and a handful of QML identifiers that
used the British spelling as their actual name: SystemSettings'
serialiseValue/serialiseTable/normaliseGradient, Displays'
normaliseModes, Wallpaper's normalisePolicy, SettingsBackup's
serialiseHomeState, DateTime's ntpSynchronised property, Clipboard's
_normalise helper, and ShortcutCapture's cancelled signal (with its
onCancelled handler in ShortcutsPage.qml). Every call site and the two
tests that assert on the literal source text (settings-ownership and
settings-backup-live contracts) were updated in lockstep.

Left untouched: config/dot/espanso/match/packages/misspell-en/ is a
vendored third-party autocorrect dictionary -- its entries are typo
corrections, not our prose, and rewriting them would fight the
package's own purpose (and any future re-sync from upstream).

The already-American `favorites` property (Home page pinned
accessories) was never actually misspelled -- only nearby comments and
error strings said "favourites" -- so no data migration was needed
there.

Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
2026-08-19 08:07:55 -04:00

282 lines
10 KiB
QML

// The month grid from GNOME's date menu.
//
// Six week rows always, so the grid height never changes as you page through
// months and the panel below it never jumps. Days belonging to the neighboring
// months fill the edges, dimmed. Today wears the accent.
//
// Days become real navigation targets now that Evolution Data Server backs the
// panel. Up to three source-colored markers show whether a date has events.
import Quickshell
import QtQuick
import qs.config
import qs.services
Item {
id: root
signal dateActivated(date value)
signal visibleMonthChanged(int year, int month)
implicitHeight: body.implicitHeight
// Cells divide the available width evenly; the height is fixed so the grid
// stays roughly square regardless of how wide the panel gets.
readonly property real cellWidth: root.width / 7
readonly property int cellHeight: 32
// Hours precision is enough: the only thing that has to change on its own
// is which cell counts as "today", and that only moves at midnight.
SystemClock {
id: clock
precision: SystemClock.Hours
}
readonly property int todayYear: clock.date.getFullYear()
readonly property int todayMonth: clock.date.getMonth()
readonly property int todayDay: clock.date.getDate()
// The month currently on screen. DateMenu calls showToday() every time the
// panel opens, so it never comes back showing wherever you paged off to.
property int viewYear: root.todayYear
property int viewMonth: root.todayMonth
property date selectedDate: CalendarAgenda.selectedDate
readonly property bool onCurrentMonth: root.viewYear === root.todayYear && root.viewMonth === root.todayMonth
function showToday(): void {
root.viewYear = root.todayYear;
root.viewMonth = root.todayMonth;
}
function stepMonth(delta: int): void {
// Built through Date rather than by hand so December -> January rolls
// the year over for us.
const d = new Date(root.viewYear, root.viewMonth + delta, 1);
root.viewYear = d.getFullYear();
root.viewMonth = d.getMonth();
}
function activateCell(model: var): void {
if (!model.current) {
root.viewYear = model.date.getFullYear();
root.viewMonth = model.date.getMonth();
}
root.dateActivated(model.date);
}
onViewYearChanged: root.visibleMonthChanged(root.viewYear, root.viewMonth)
onViewMonthChanged: root.visibleMonthChanged(root.viewYear, root.viewMonth)
readonly property var cells: {
const first = new Date(root.viewYear, root.viewMonth, 1);
const offset = first.getDay(); // 0 = Sunday, matching the header row
const out = [];
for (let i = 0; i < 42; i++) {
const date = new Date(root.viewYear, root.viewMonth, i - offset + 1);
out.push({
date: date,
day: date.getDate(),
current: date.getMonth() === root.viewMonth && date.getFullYear() === root.viewYear
});
}
return out;
}
Column {
id: body
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: 6
// ── Month header ────────────────────────────────────────────────────
// Both arrows sit on the right so the left slot is free for the "Today"
// affordance, which only exists while you are looking at another month.
Item {
width: parent.width
height: 30
Rectangle {
id: todayPill
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: todayLabel.implicitWidth + 18
height: 24
radius: Theme.pillRadius
border.width: 0
visible: !root.onCurrentMonth
color: todayMouse.containsMouse ? Theme.alpha(Theme.accent, 0.30) : Theme.alpha(Theme.accent, 0.16)
Behavior on color {
ColorAnimation {
duration: todayMouse.containsMouse ? Theme.durFast : Theme.durNormal
}
}
Text {
id: todayLabel
anchors.centerIn: parent
text: "Today"
color: Theme.accent
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
}
MouseArea {
id: todayMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.showToday()
}
}
// Centered on the row rather than between the pill and the arrows,
// so it does not shift sideways when the pill appears.
Text {
anchors.centerIn: parent
text: Qt.formatDateTime(new Date(root.viewYear, root.viewMonth, 1), "MMMM yyyy")
font.family: Theme.fontFamily
// The year ticks in place when you page across a boundary.
font.features: Theme.tabularFigures
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.DemiBold
color: Theme.fg
}
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 2
MonthArrow {
glyph: "\u{F0141}" // md-chevron_left
onActivated: root.stepMonth(-1)
}
MonthArrow {
glyph: "\u{F0142}" // md-chevron_right
onActivated: root.stepMonth(1)
}
}
}
// ── Weekday header ──────────────────────────────────────────────────
Row {
width: parent.width
Repeater {
model: ["S", "M", "T", "W", "T", "F", "S"]
delegate: Text {
required property string modelData
width: root.cellWidth
height: 22
text: modelData
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.fgMuted
}
}
}
// ── Day grid ────────────────────────────────────────────────────────
Grid {
width: parent.width
columns: 7
Repeater {
model: root.cells
delegate: Item {
id: cell
required property var modelData
readonly property bool isToday: cell.modelData.current && cell.modelData.day === root.todayDay && root.onCurrentMonth
readonly property bool isSelected: CalendarAgenda.dateKey(cell.modelData.date) === CalendarAgenda.dateKey(root.selectedDate)
readonly property var markers: CalendarAgenda.markersForDate(cell.modelData.date).slice(0, 3)
width: root.cellWidth
height: root.cellHeight
Rectangle {
anchors.centerIn: parent
width: root.cellHeight - 4
height: root.cellHeight - 4
radius: width / 2
border.width: 0
visible: cell.isToday || cell.isSelected || cellMouse.containsMouse
color: {
if (cell.isToday)
return Theme.accent;
if (cell.isSelected)
return Theme.alpha(Theme.accent, 0.20);
return Theme.alpha(Theme.fg, 0.08);
}
Behavior on color {
ColorAnimation { duration: Theme.durFast }
}
}
Text {
anchors.centerIn: parent
text: cell.modelData.day
font.family: Theme.fontFamily
font.features: Theme.tabularFigures
font.pixelSize: Theme.fontSize
font.weight: cell.isToday ? Font.DemiBold : Font.Normal
color: {
if (cell.isToday)
return Theme.bgDark;
if (cell.isSelected)
return Theme.accent;
return cell.modelData.current ? Theme.fg : Theme.fgMuted;
}
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 1
spacing: 2
Repeater {
model: cell.markers
Rectangle {
required property var modelData
width: 3
height: 3
radius: 2
border.width: 0
color: cell.isToday ? Theme.bgDark : modelData.color
}
}
}
MouseArea {
id: cellMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.activateCell(cell.modelData)
}
}
}
}
}
Component.onCompleted: root.visibleMonthChanged(root.viewYear, root.viewMonth)
}