diff --git a/initialstart/modules/time/package/contents/ui/main.qml b/initialstart/modules/time/package/contents/ui/main.qml index 00233c1d..22b8b3cb 100644 --- a/initialstart/modules/time/package/contents/ui/main.qml +++ b/initialstart/modules/time/package/contents/ui/main.qml @@ -18,90 +18,129 @@ InitialStartModule { readonly property real cardWidth: Math.min(Kirigami.Units.gridUnit * 30, root.width - Kirigami.Units.gridUnit * 2) - ColumnLayout { + function selectedTimeZoneName() { + const timeZone = Time.TimeUtil.currentTimeZone.replace(/_/g, " "); + const parts = timeZone.split("/").filter(part => part.length > 0); + if (parts.length <= 1) { + return timeZone; + } + return i18nc("timezone city and region", "%1 (%2)", parts.slice(1).join(" / "), parts[0]); + } + + ScrollView { anchors { fill: parent topMargin: Kirigami.Units.gridUnit - bottomMargin: Kirigami.Units.largeSpacing } - width: root.width - spacing: Kirigami.Units.gridUnit + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + contentWidth: -1 - Label { - Layout.leftMargin: Kirigami.Units.gridUnit - Layout.rightMargin: Kirigami.Units.gridUnit - Layout.alignment: Qt.AlignTop - Layout.fillWidth: true + ColumnLayout { + width: root.width + spacing: Kirigami.Units.gridUnit - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignHCenter - text: i18n("Select your time zone and preferred time format.") - } - - FormCard.FormCard { - maximumWidth: root.cardWidth - - Layout.alignment: Qt.AlignTop | Qt.AlignHCenter - Layout.fillWidth: true - - FormCard.FormSwitchDelegate { + Label { + Layout.leftMargin: Kirigami.Units.gridUnit + Layout.rightMargin: Kirigami.Units.gridUnit + Layout.alignment: Qt.AlignTop Layout.fillWidth: true - text: i18n("24-Hour Format") - checked: Time.TimeUtil.is24HourTime - onCheckedChanged: { - if (checked !== Time.TimeUtil.is24HourTime) { - Time.TimeUtil.is24HourTime = checked; + + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + text: i18n("Select your time zone and preferred time format.") + } + + FormCard.FormCard { + maximumWidth: root.cardWidth + + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.fillWidth: true + + FormCard.FormSwitchDelegate { + Layout.fillWidth: true + text: i18n("24-Hour Format") + checked: Time.TimeUtil.is24HourTime + onCheckedChanged: { + if (checked !== Time.TimeUtil.is24HourTime) { + Time.TimeUtil.is24HourTime = checked; + } + } + } + + } + + FormCard.FormCard { + maximumWidth: root.cardWidth + + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.fillWidth: true + + FormCard.FormTextDelegate { + text: i18n("Selected time zone") + description: root.selectedTimeZoneName() + } + } + + FormCard.FormCard { + maximumWidth: root.cardWidth + + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.fillWidth: true + Layout.preferredHeight: Math.max(Kirigami.Units.gridUnit * 14, root.height - Kirigami.Units.gridUnit * 18) + + ListView { + id: listView + + clip: true + anchors.fill: parent + model: Time.TimeUtil.timeZones + currentIndex: -1 // ensure focus is not on the listview + + header: Control { + width: listView.width + leftPadding: Kirigami.Units.largeSpacing + rightPadding: Kirigami.Units.largeSpacing + topPadding: Kirigami.Units.largeSpacing + bottomPadding: Kirigami.Units.largeSpacing + + contentItem: Kirigami.SearchField { + id: searchField + + placeholderText: i18n("Search city or region") + + onTextChanged: { + Time.TimeUtil.timeZones.filterString = text; + } + } + } + + delegate: FormCard.FormRadioDelegate { + required property string timeZoneId + required property string displayName + + width: ListView.view.width + text: displayName + description: timeZoneId + onClicked: { + Time.TimeUtil.currentTimeZone = timeZoneId; + } + + Binding on checked { + value: Time.TimeUtil.currentTimeZone === timeZoneId + } } } } - } - FormCard.FormCard { - maximumWidth: root.cardWidth - - Layout.fillHeight: true - Layout.alignment: Qt.AlignTop | Qt.AlignHCenter - Layout.fillWidth: true - - ListView { - id: listView - - clip: true + Label { + visible: Time.TimeUtil.timeZoneStatus.length > 0 + Layout.leftMargin: Kirigami.Units.gridUnit + Layout.rightMargin: Kirigami.Units.gridUnit Layout.fillWidth: true - Layout.fillHeight: true - model: Time.TimeUtil.timeZones - currentIndex: -1 // ensure focus is not on the listview - - header: Control { - width: listView.width - leftPadding: Kirigami.Units.largeSpacing - rightPadding: Kirigami.Units.largeSpacing - topPadding: Kirigami.Units.largeSpacing - bottomPadding: Kirigami.Units.largeSpacing - - contentItem: Kirigami.SearchField { - id: searchField - - onTextChanged: { - Time.TimeUtil.timeZones.filterString = text; - } - } - } - - delegate: FormCard.FormRadioDelegate { - required property string timeZoneId - - width: ListView.view.width - text: timeZoneId - checked: Time.TimeUtil.currentTimeZone === timeZoneId - onCheckedChanged: { - if (checked && timeZoneId !== Time.TimeUtil.currentTimeZone) { - Time.TimeUtil.currentTimeZone = timeZoneId; - checked = Qt.binding(() => Time.TimeUtil.currentTimeZone === timeZoneId); - } - } - } + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + text: Time.TimeUtil.timeZoneStatus } } } diff --git a/initialstart/modules/time/timeutil.cpp b/initialstart/modules/time/timeutil.cpp index 47595f8c..89223dc9 100644 --- a/initialstart/modules/time/timeutil.cpp +++ b/initialstart/modules/time/timeutil.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #define FORMAT24H "HH:mm:ss" @@ -15,9 +16,14 @@ TimeUtil::TimeUtil(QObject *parent) : QObject{parent} + , m_currentTimeZone{QString::fromUtf8(QTimeZone::systemTimeZoneId())} , m_timeZoneModel{new TimeZoneModel{this}} , m_filterModel{new TimeZoneFilterProxy{this}} { + if (m_currentTimeZone.isEmpty() || !QTimeZone(m_currentTimeZone.toUtf8()).isValid()) { + m_currentTimeZone = QStringLiteral("UTC"); + } + m_filterModel->setSourceModel(m_timeZoneModel); // retrieve is24HourTime @@ -46,13 +52,36 @@ void TimeUtil::setIs24HourTime(bool is24HourTime) QString TimeUtil::currentTimeZone() const { - return QString{QTimeZone::systemTimeZoneId()}; + return m_currentTimeZone; } void TimeUtil::setCurrentTimeZone(QString timeZone) { - QProcess::execute("timedatectl", {"set-timezone", timeZone}); + if (timeZone == m_currentTimeZone) { + return; + } + + const QTimeZone zone(timeZone.toUtf8()); + if (!zone.isValid()) { + m_timeZoneStatus = i18n("This time zone is not available."); + Q_EMIT timeZoneStatusChanged(); + return; + } + + m_currentTimeZone = timeZone; Q_EMIT currentTimeZoneChanged(); + + const int exitCode = QProcess::execute(QStringLiteral("timedatectl"), {QStringLiteral("set-timezone"), timeZone}); + const QString status = exitCode == 0 ? QString() : i18n("Selected here, but the system time zone could not be changed."); + if (status != m_timeZoneStatus) { + m_timeZoneStatus = status; + Q_EMIT timeZoneStatusChanged(); + } +} + +QString TimeUtil::timeZoneStatus() const +{ + return m_timeZoneStatus; } TimeZoneFilterProxy *TimeUtil::timeZones() const diff --git a/initialstart/modules/time/timeutil.h b/initialstart/modules/time/timeutil.h index dbd06a31..80529d59 100644 --- a/initialstart/modules/time/timeutil.h +++ b/initialstart/modules/time/timeutil.h @@ -13,6 +13,7 @@ class TimeUtil : public QObject Q_OBJECT Q_PROPERTY(bool is24HourTime READ is24HourTime WRITE setIs24HourTime NOTIFY is24HourTimeChanged); Q_PROPERTY(QString currentTimeZone READ currentTimeZone WRITE setCurrentTimeZone NOTIFY currentTimeZoneChanged); + Q_PROPERTY(QString timeZoneStatus READ timeZoneStatus NOTIFY timeZoneStatusChanged); Q_PROPERTY(TimeZoneFilterProxy *timeZones READ timeZones CONSTANT); public: @@ -24,14 +25,19 @@ public: QString currentTimeZone() const; void setCurrentTimeZone(QString timeZone); + QString timeZoneStatus() const; + TimeZoneFilterProxy *timeZones() const; Q_SIGNALS: void is24HourTimeChanged(); void currentTimeZoneChanged(); + void timeZoneStatusChanged(); private: - bool m_is24HourTime; + bool m_is24HourTime = false; + QString m_currentTimeZone; + QString m_timeZoneStatus; TimeZoneModel *m_timeZoneModel; TimeZoneFilterProxy *m_filterModel; diff --git a/initialstart/modules/time/timezonemodel.cpp b/initialstart/modules/time/timezonemodel.cpp index 417fe072..3ea17b8c 100644 --- a/initialstart/modules/time/timezonemodel.cpp +++ b/initialstart/modules/time/timezonemodel.cpp @@ -8,9 +8,13 @@ #include #include +#include +#include #include #include +#include + TimeZoneFilterProxy::TimeZoneFilterProxy(QObject *parent) : QSortFilterProxyModel(parent) { @@ -22,8 +26,10 @@ bool TimeZoneFilterProxy::filterAcceptsRow(int source_row, const QModelIndex &so if (!sourceModel() || m_filterString.isEmpty()) { return true; } - const QString id = sourceModel()->index(source_row, 0, source_parent).data(TimeZoneModel::TimeZoneIdRole).toString(); - return m_stringMatcher.indexIn(id) != -1; + const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); + const QString id = index.data(TimeZoneModel::TimeZoneIdRole).toString(); + const QString displayName = index.data(TimeZoneModel::DisplayNameRole).toString(); + return m_stringMatcher.indexIn(id) != -1 || m_stringMatcher.indexIn(displayName) != -1; } void TimeZoneFilterProxy::setFilterString(const QString &filterString) @@ -56,6 +62,8 @@ QVariant TimeZoneModel::data(const QModelIndex &index, int role) const switch (role) { case TimeZoneIdRole: return m_data[index.row()]; + case DisplayNameRole: + return displayNameForId(m_data[index.row()]); } } @@ -67,16 +75,28 @@ void TimeZoneModel::update() beginResetModel(); m_data.clear(); - QTimeZone localZone = QTimeZone(QTimeZone::systemTimeZoneId()); - m_data.append(localZone.id()); - - QStringList cities; - QHash zonesByCity; + QString localZone = QString::fromUtf8(QTimeZone::systemTimeZoneId()); + if (localZone.isEmpty() || !QTimeZone(localZone.toUtf8()).isValid()) { + localZone = QStringLiteral("UTC"); + } + QSet seenZones; const QList systemTimeZones = QTimeZone::availableTimeZoneIds(); + for (const QByteArray &timeZoneId : systemTimeZones) { + const QString zone = QString::fromUtf8(timeZoneId); + if (!seenZones.contains(zone)) { + seenZones.insert(zone); + m_data.append(zone); + } + } - for (QByteArray arr : systemTimeZones) { - m_data.append(arr); + std::sort(m_data.begin(), m_data.end(), [](const QString &left, const QString &right) { + return QString::localeAwareCompare(left, right) < 0; + }); + + const int localIndex = m_data.indexOf(localZone); + if (localIndex > 0) { + m_data.move(localIndex, 0); } endResetModel(); @@ -84,5 +104,18 @@ void TimeZoneModel::update() QHash TimeZoneModel::roleNames() const { - return {{TimeZoneIdRole, "timeZoneId"}}; + return {{TimeZoneIdRole, "timeZoneId"}, {DisplayNameRole, "displayName"}}; +} + +QString TimeZoneModel::displayNameForId(const QString &timeZoneId) const +{ + QString displayName = timeZoneId; + displayName.replace(QLatin1Char('_'), QLatin1Char(' ')); + + const QStringList parts = displayName.split(QLatin1Char('/'), Qt::SkipEmptyParts); + if (parts.size() <= 1) { + return displayName; + } + + return i18nc("timezone city and region", "%1 (%2)", parts.mid(1).join(QStringLiteral(" / ")), parts.first()); } diff --git a/initialstart/modules/time/timezonemodel.h b/initialstart/modules/time/timezonemodel.h index 1f20f411..f8ae7977 100644 --- a/initialstart/modules/time/timezonemodel.h +++ b/initialstart/modules/time/timezonemodel.h @@ -36,7 +36,10 @@ public: explicit TimeZoneModel(QObject *parent = nullptr); ~TimeZoneModel() override; - enum Roles { TimeZoneIdRole = Qt::UserRole + 1 }; + enum Roles { + TimeZoneIdRole = Qt::UserRole + 1, + DisplayNameRole + }; int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; @@ -47,5 +50,7 @@ protected: QHash roleNames() const override; private: + QString displayNameForId(const QString &timeZoneId) const; + QList m_data; };