From e8e3b7ef54f62e1f69f5bb77541ee29793b2c1b0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 22 Jun 2026 20:52:02 +0200 Subject: [PATCH] Fix weather panel clipping and units --- shell/Ui/KeyboardPanel.qml | 6 +++-- shell/Ui/PopupCard.qml | 3 ++- shell/plugins/panels/weather/Model.js | 35 ++++++++++++++++++++++++++ shell/plugins/panels/weather/Panel.qml | 9 ++----- test/shell.d/weather-test.sh | 5 ++++ 5 files changed, 48 insertions(+), 10 deletions(-) diff --git a/shell/Ui/KeyboardPanel.qml b/shell/Ui/KeyboardPanel.qml index b861b8a2..e590d7f1 100644 --- a/shell/Ui/KeyboardPanel.qml +++ b/shell/Ui/KeyboardPanel.qml @@ -40,6 +40,7 @@ PanelWindow { property int padding: Style.spacing.popupPadding property int contentWidth: Style.space(280) property int contentHeight: Style.space(200) + property var borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2))) property bool centerOnBar: false property bool open: false property int gap: Style.gapsOut // distance between bar edge and panel @@ -135,6 +136,7 @@ PanelWindow { readonly property real availableCardHeight: screenH > 0 ? Math.max(120, screenH - ((barPos === "top" || barPos === "bottom") ? barH + gap + margin : margin * 2)) : 0 + readonly property real verticalContentInset: padding * 2 + Border.top(borderSpec) + Border.bottom(borderSpec) function fittedContentWidth(width, cap) { var desired = Math.max(1, Number(width) || 1) @@ -144,7 +146,7 @@ PanelWindow { } function fittedContentHeight(implicitHeight, cap) { - var desired = Math.max(root.padding * 2, (Number(implicitHeight) || 0) + root.padding * 2) + var desired = Math.max(root.verticalContentInset, (Number(implicitHeight) || 0) + root.verticalContentInset) var maxHeight = root.availableCardHeight > 0 ? root.availableCardHeight : desired if (cap !== undefined && Number(cap) > 0) maxHeight = Math.min(maxHeight, Number(cap)) return Math.round(Math.min(desired, maxHeight)) @@ -297,7 +299,7 @@ PanelWindow { width: root.contentWidth height: root.contentHeight color: Color.popups.background - borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border, Math.max(1, Style.space(2))) + borderSpec: root.borderSpec padding: root.padding radius: Style.cornerRadius opacity: root.open || root.popoutSwitching ? 1.0 : 0 diff --git a/shell/Ui/PopupCard.qml b/shell/Ui/PopupCard.qml index ef72fe16..058b068f 100644 --- a/shell/Ui/PopupCard.qml +++ b/shell/Ui/PopupCard.qml @@ -35,6 +35,7 @@ PopupWindow { readonly property real availableCardHeight: screenH > 0 ? Math.max(120, screenH - ((bar && (bar.position === "top" || bar.position === "bottom")) ? barH : 0) - root.margin * 2) : 0 + readonly property real verticalContentInset: padding * 2 + Border.top(borderSpec) + Border.bottom(borderSpec) function fittedContentWidth(width, cap) { var desired = Math.max(1, Number(width) || 1) @@ -44,7 +45,7 @@ PopupWindow { } function fittedContentHeight(implicitHeight, cap) { - var desired = Math.max(root.padding * 2, (Number(implicitHeight) || 0) + root.padding * 2) + var desired = Math.max(root.verticalContentInset, (Number(implicitHeight) || 0) + root.verticalContentInset) var maxHeight = root.availableCardHeight > 0 ? root.availableCardHeight : desired if (cap !== undefined && Number(cap) > 0) maxHeight = Math.min(maxHeight, Number(cap)) return Math.round(Math.min(desired, maxHeight)) diff --git a/shell/plugins/panels/weather/Model.js b/shell/plugins/panels/weather/Model.js index 2ccb3321..7e79b88b 100644 --- a/shell/plugins/panels/weather/Model.js +++ b/shell/plugins/panels/weather/Model.js @@ -32,6 +32,37 @@ function formatTemp(value, useImperial) { return value + "°" + (useImperial ? "F" : "C") } +function normalizedUnit(value) { + return String(value || "").replace(/^\s+|\s+$/g, "").toLowerCase() +} + +function localeUsesImperial(localeName) { + var name = String(localeName || "").replace(".", "_") + return /^en[_-]US($|[_.-])/.test(name) || /^en[_-]LR($|[_.-])/.test(name) || /^my($|[_.-])/.test(name) +} + +function countryUsesImperial(countryName) { + var country = String(countryName || "") + .replace(/^\s+|\s+$/g, "") + .replace(/[._-]+/g, " ") + .toLowerCase() + if (!country) return null + if (country === "us" || country === "usa" || country === "united states" || country === "united states of america") return true + if (country === "liberia" || country === "myanmar" || country === "burma") return true + return false +} + +function shouldUseImperial(unitOverride, localeName, countryName) { + var unit = normalizedUnit(unitOverride) + if (unit === "imperial") return true + if (unit === "metric") return false + + var countryPreference = countryUsesImperial(countryName) + if (countryPreference !== null) return countryPreference + + return localeUsesImperial(localeName) +} + function dayName(dateString, formatter) { if (!dateString) return "" var d = new Date(dateString + "T12:00:00") @@ -143,6 +174,10 @@ if (typeof module !== "undefined") { roundedTemp: roundedTemp, celsiusToFahrenheit: celsiusToFahrenheit, formatTemp: formatTemp, + normalizedUnit: normalizedUnit, + localeUsesImperial: localeUsesImperial, + countryUsesImperial: countryUsesImperial, + shouldUseImperial: shouldUseImperial, dayName: dayName, openMeteoForecastDays: openMeteoForecastDays, wttrNextForecastDays: wttrNextForecastDays, diff --git a/shell/plugins/panels/weather/Panel.qml b/shell/plugins/panels/weather/Panel.qml index 2e27d735..71d6a31f 100644 --- a/shell/plugins/panels/weather/Panel.qml +++ b/shell/plugins/panels/weather/Panel.qml @@ -61,14 +61,9 @@ Panel { readonly property var current: report && report.current_condition && report.current_condition[0] ? report.current_condition[0] : null readonly property var areaInfo: report && report.nearest_area && report.nearest_area[0] ? report.nearest_area[0] : null readonly property var forecastDays: buildForecastDays() + readonly property string reportCountry: areaInfo && areaInfo.country && areaInfo.country[0] ? areaInfo.country[0].value : "" - readonly property bool useImperial: { - var override = setting("unit", "") - if (override === "imperial") return true - if (override === "metric") return false - var name = String(Qt.locale().name || "") - return /^en_US/.test(name) || /^en_LR/.test(name) || /^my/.test(name) - } + readonly property bool useImperial: Model.shouldUseImperial(setting("unit", ""), Qt.locale().name, reportCountry) // Auto-refresh interval in minutes; clamped to a sane minimum. readonly property int refreshMinutes: Math.max(1, parseInt(setting("refreshMinutes", 15), 10) || 15) diff --git a/test/shell.d/weather-test.sh b/test/shell.d/weather-test.sh index 889afa58..4f6e4ebe 100644 --- a/test/shell.d/weather-test.sh +++ b/test/shell.d/weather-test.sh @@ -18,6 +18,11 @@ assertEqual(weather.roundedTemp('21.6'), '22', 'weather rounds temperatures') assertEqual(weather.roundedTemp('nope'), '', 'weather ignores invalid temperatures') assertEqual(weather.formatTemp(72, true), '72°F', 'weather formats imperial temperatures') assertEqual(weather.formatTemp(22, false), '22°C', 'weather formats metric temperatures') +assertEqual(weather.shouldUseImperial('', 'en_US', ''), true, 'weather falls back to US locale for imperial units') +assertEqual(weather.shouldUseImperial('', 'en_US', 'Denmark'), false, 'weather prefers reported metric country over US locale') +assertEqual(weather.shouldUseImperial('', 'da_DK', 'United States of America'), true, 'weather prefers reported imperial country over metric locale') +assertEqual(weather.shouldUseImperial('metric', 'en_US', 'United States of America'), false, 'weather metric override wins') +assertEqual(weather.shouldUseImperial('imperial', 'da_DK', 'Denmark'), true, 'weather imperial override wins') assertEqual(weather.dayName('2026-05-25'), 'Monday', 'weather derives day names') const openMeteo = {