Add a clock format with live seconds (#7586)
* Add a clock format with live seconds Right-clicking the clock now reaches "Thursday 09:39:23" and its AM/PM twin, and the widget's SystemClock ticks once a second only while a format that prints seconds is showing — every other format keeps the minute precision it had, so nobody pays for a repaint a second to read a label that changes once a minute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Read an unterminated literal in a clock format as text Qt reads an opening quote with no closing one as a literal running to the end of the format, so "HH:mm 'sec" prints "09:39 sec" and never a second count — but the seconds test stripped only balanced quotes, saw the s, and put the widget on a per-second tick for a label that changes once a minute. The wiring assertions went the other way: each passed while the feature was broken, so hard-coding showsSeconds to false, dropping the label's onDateChanged, or commenting the precision line out and leaving the text behind all shipped green. Comments now come out of the source before it is matched, and both halves of the tick are asserted. Co-Authored-By: Codex XHigh <noreply@openai.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Codex XHigh <noreply@openai.com>
This commit is contained in:
co-authored by
Claude Opus 5
Codex XHigh
parent
688d7df5d2
commit
d3d9bea1ee
@@ -28,6 +28,10 @@ BarWidget {
|
||||
// What the bar shows is what shell.json stores, so a cycled format is the
|
||||
// format from then on rather than something that reverts on restart.
|
||||
readonly property string activeFormat: configuredFormat
|
||||
|
||||
// A seconds label needs the clock to tick sixty times as often, and a
|
||||
// repaint a second is a price only the formats that print seconds pay.
|
||||
readonly property bool showsSeconds: Model.clockNeedsSeconds(activeFormat)
|
||||
readonly property string displayText: formatted(displayDate)
|
||||
readonly property var verticalLines: displayText.split("\n")
|
||||
|
||||
@@ -111,7 +115,7 @@ BarWidget {
|
||||
|
||||
SystemClock {
|
||||
id: clock
|
||||
precision: SystemClock.Minutes
|
||||
precision: root.showsSeconds ? SystemClock.Seconds : SystemClock.Minutes
|
||||
onDateChanged: root.displayDate = date
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ var WEEKDAY_NAMES = ["sunday", "monday", "tuesday", "wednesday", "thursday", "fr
|
||||
var CLOCK_FORMATS = [
|
||||
"dddd HH:mm",
|
||||
"dddd h:mm AP",
|
||||
"dddd HH:mm:ss",
|
||||
"dddd h:mm:ss AP",
|
||||
"HH:mm",
|
||||
"h:mm AP",
|
||||
"ddd d MMM HH:mm",
|
||||
@@ -39,6 +41,15 @@ var VERTICAL_CLOCK_FORMATS = [
|
||||
"HH\nmm"
|
||||
]
|
||||
|
||||
// Whether a format prints seconds, so the widget can tick once a second only
|
||||
// for the formats that show them. Quoted literals go first: the s in a 'Sat'
|
||||
// is text rather than a token, and an opening quote with no closing one runs
|
||||
// to the end of the format the way Qt reads it.
|
||||
function clockNeedsSeconds(format) {
|
||||
var text = String(format === undefined || format === null ? "" : format)
|
||||
return /s/.test(text.replace(/'[^']*'?/g, ""))
|
||||
}
|
||||
|
||||
function clockFormats(vertical) {
|
||||
return vertical ? VERTICAL_CLOCK_FORMATS.slice() : CLOCK_FORMATS.slice()
|
||||
}
|
||||
@@ -289,6 +300,7 @@ if (typeof module !== "undefined") {
|
||||
monthGrid: monthGrid,
|
||||
stepMonth: stepMonth,
|
||||
clockFormats: clockFormats,
|
||||
clockNeedsSeconds: clockNeedsSeconds,
|
||||
clockFormatRing: clockFormatRing,
|
||||
nextClockFormat: nextClockFormat,
|
||||
isoWeekLiteral: isoWeekLiteral
|
||||
|
||||
@@ -8,7 +8,10 @@ run_node_test <<'JS'
|
||||
const fs = require('fs')
|
||||
const calendar = requireFromRoot('shell/plugins/panels/clock/Model.js')
|
||||
const panelSource = fs.readFileSync(root + '/shell/plugins/panels/clock/Panel.qml', 'utf8')
|
||||
// Comments stripped: a wiring assertion that a commented-out line can satisfy
|
||||
// passes while the widget is broken.
|
||||
const widgetSource = fs.readFileSync(root + '/shell/plugins/panels/clock/BarWidget.qml', 'utf8')
|
||||
.replace(/^\s*\/\/.*$/gm, '')
|
||||
|
||||
// ---- week start resolution
|
||||
assertEqual(calendar.normalizedWeekStart('monday', 0), 1, 'calendar reads a named week start')
|
||||
@@ -145,6 +148,7 @@ assertDeepEqual(
|
||||
calendar.clockFormats(false),
|
||||
[
|
||||
'dddd HH:mm', 'dddd h:mm AP',
|
||||
'dddd HH:mm:ss', 'dddd h:mm:ss AP',
|
||||
'HH:mm', 'h:mm AP',
|
||||
'ddd d MMM HH:mm', 'ddd d MMM h:mm AP',
|
||||
"d MMMM 'W'ww yyyy",
|
||||
@@ -166,6 +170,23 @@ assertEqual(
|
||||
)
|
||||
assertEqual(calendar.isoWeekLiteral(2026, 0, 5), '02', 'clock zero-pads the ISO week token')
|
||||
|
||||
// ---- seconds detection, which decides how often the widget's clock ticks
|
||||
assert(calendar.clockNeedsSeconds('dddd HH:mm:ss'), 'clock sees seconds in the live preset')
|
||||
assert(calendar.clockNeedsSeconds('h:mm:ss AP'), 'clock sees seconds in an AM/PM format')
|
||||
assert(!calendar.clockNeedsSeconds('dddd HH:mm'), 'clock sees no seconds in a minute format')
|
||||
assert(!calendar.clockNeedsSeconds("d MMMM 'W'ww yyyy"), 'clock sees no seconds in the long date format')
|
||||
assert(!calendar.clockNeedsSeconds("dd\nMMM\n'W'ww\n''yy"), 'clock sees no seconds in the stacked date format')
|
||||
assert(!calendar.clockNeedsSeconds("HH:mm 'since'"), 'clock reads an s inside a quoted literal as text')
|
||||
// Qt reads an opening quote with no closing one as a literal running to the end
|
||||
// of the format, so "HH:mm 'sec" prints "09:39 sec" and never a second count.
|
||||
assert(!calendar.clockNeedsSeconds("HH:mm 'sec"), 'clock reads an unterminated literal as text to the end')
|
||||
assert(!calendar.clockNeedsSeconds("HH:mm's"), 'clock reads a trailing unterminated literal as text')
|
||||
// The doubled quote is a literal apostrophe rather than an empty literal, so
|
||||
// the s after it is still the seconds token: Qt prints "'23".
|
||||
assert(calendar.clockNeedsSeconds("''s"), 'clock still sees seconds after an escaped apostrophe')
|
||||
assert(!calendar.clockNeedsSeconds(''), 'clock sees no seconds in an empty format')
|
||||
assert(!calendar.clockNeedsSeconds(null), 'clock sees no seconds in a missing format')
|
||||
|
||||
// ---- widget wiring
|
||||
assert(/moduleName: "omarchy\.clock"/.test(panelSource), 'calendar panel declares its module name')
|
||||
assert(/ipcTarget: "omarchy\.clock"/.test(panelSource), 'calendar panel registers its IPC target')
|
||||
@@ -173,6 +194,12 @@ assert(/manageIpc: false/.test(panelSource), 'calendar panel leaves the IPC targ
|
||||
assert(/anchorItem: root\.anchorItem/.test(panelSource), 'calendar panel anchors to the host widget button')
|
||||
assert(/function toggleWeekStart\(\)/.test(panelSource), 'calendar panel exposes a week start toggle')
|
||||
assert(/function toggleWeekStart\(\): void \{ root\.toggleWeekStart\(\) \}/.test(widgetSource), 'clock exposes the week start toggle over IPC')
|
||||
assert(/precision: root\.showsSeconds \? SystemClock\.Seconds : SystemClock\.Minutes/.test(widgetSource), 'clock ticks per second only for a format that prints seconds')
|
||||
// Both halves of the tick, because either one alone can be true while the
|
||||
// label sits frozen: the precision must follow the format, and every tick
|
||||
// must reach the label.
|
||||
assert(/showsSeconds: Model\.clockNeedsSeconds\(activeFormat\)/.test(widgetSource), 'clock decides its tick rate from the format it is showing')
|
||||
assert(/onDateChanged: root\.displayDate = date/.test(widgetSource), 'clock repaints the label on every tick')
|
||||
assert(/setting\("weekStartDay", null\)/.test(panelSource) && /persistSettings\(\{ weekStartDay:/.test(panelSource), 'calendar reads and writes the week start as weekStartDay')
|
||||
assert(/updateEntryInline/.test(panelSource), 'calendar panel persists the week start to shell.json')
|
||||
assert(/function moveMonth\(delta\)/.test(panelSource), 'calendar panel steps between months')
|
||||
|
||||
Reference in New Issue
Block a user