From d10e0859822eb942224591f8d21b8d43029cbed4 Mon Sep 17 00:00:00 2001 From: Zhora Zmeykin Date: Thu, 3 Sep 2026 14:37:44 +0300 Subject: [PATCH] Restore legacy timezone selector in kcm_clock --- CMakeLists.txt | 5 + kcms/dateandtime/CMakeLists.txt | 8 +- kcms/dateandtime/dateandtime.ui | 58 +++++- kcms/dateandtime/dtime.cpp | 42 +++-- kcms/dateandtime/k4timezonewidget.cpp | 262 ++++++++++++++++++++++++++ kcms/dateandtime/k4timezonewidget.h | 139 ++++++++++++++ kcms/dateandtime/ui/CMakeLists.txt | 5 - kcms/dateandtime/ui/main.qml | 24 --- po/ru/kcmkclock.po | 75 +++++--- 9 files changed, 539 insertions(+), 79 deletions(-) create mode 100644 plasma-desktop/kcms/dateandtime/k4timezonewidget.cpp create mode 100644 plasma-desktop/kcms/dateandtime/k4timezonewidget.h delete mode 100644 plasma-desktop/kcms/dateandtime/ui/CMakeLists.txt delete mode 100644 plasma-desktop/kcms/dateandtime/ui/main.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b04228e..dc6e8dd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -235,6 +235,11 @@ if (BUILD_KCM_TOUCHPAD_X11) ) endif() +find_package(ICU COMPONENTS i18n uc) +set_package_properties(ICU PROPERTIES + PURPOSE "Retrieving timezone info" + TYPE REQUIRED +) set_package_properties(XCB PROPERTIES TYPE REQUIRED) add_feature_info("XCB-XKB" XCB_XKB_FOUND "Required for building kcm/keyboard") diff --git a/kcms/dateandtime/CMakeLists.txt b/kcms/dateandtime/CMakeLists.txt index 96d37719..f6a2c60a 100644 --- a/kcms/dateandtime/CMakeLists.txt +++ b/kcms/dateandtime/CMakeLists.txt @@ -2,7 +2,7 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kcmkclock\") ########### next target ############### -set(kcm_clock_PART_SRCS dtime.cpp main.cpp) +set(kcm_clock_PART_SRCS dtime.cpp main.cpp k4timezonewidget.cpp) ki18n_wrap_ui(kcm_clock_PART_SRCS dateandtime.ui) @@ -11,12 +11,8 @@ qt_add_dbus_interface(kcm_clock_PART_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/timedated1 add_library(kcm_clock MODULE ${kcm_clock_PART_SRCS}) kcmutils_generate_desktop_file(kcm_clock) -add_subdirectory( ui ) - target_link_libraries(kcm_clock Qt::DBus - Qt::Quick - Qt::QuickWidgets KF6::ItemViews KF6::GuiAddons KF6::KCMUtils @@ -25,5 +21,7 @@ target_link_libraries(kcm_clock KF6::KIOCore KF6::KIOWidgets KF6::Svg + ICU::i18n + ICU::uc ) install(TARGETS kcm_clock DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/kcms/systemsettings_qwidgets ) diff --git a/kcms/dateandtime/dateandtime.ui b/kcms/dateandtime/dateandtime.ui index 3d8e1b52..c664ecab 100644 --- a/kcms/dateandtime/dateandtime.ui +++ b/kcms/dateandtime/dateandtime.ui @@ -135,16 +135,66 @@ - + Time Zone + + + + + To change the local time zone, select your area from the list below. + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Fixed + + + + 20 + 5 + + + + + + + + Current local time zone: + + + + + + + true + + + Search… + + + + + + + + + K4TimeZoneWidget + QTreeWidget +
k4timezonewidget.h
+
KDatePicker QFrame @@ -156,9 +206,9 @@
kseparator.h
- QQuickWidget - QWidget -
QtQuickWidgets/QQuickWidget
+ KTreeWidgetSearchLine + QLineEdit +
ktreewidgetsearchline.h
diff --git a/kcms/dateandtime/dtime.cpp b/kcms/dateandtime/dtime.cpp index f53c6f77..b0ffc243 100644 --- a/kcms/dateandtime/dtime.cpp +++ b/kcms/dateandtime/dtime.cpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include @@ -30,11 +30,9 @@ #include #include #include -#include #include #include -#include #include #include "timedated_interface.h" @@ -82,21 +80,30 @@ Dtime::Dtime(QWidget *parent) tabWidget->tabBar()->setExpanding(true); - auto engine = timezoneViewer->engine(); - engine->rootContext()->setContextObject(new KLocalizedContext(engine)); - - timezoneViewer->rootContext()->setContextProperty("DTime", this); - timezoneViewer->setSource(QUrl("qrc:/kcm/kcm_clock/main.qml")); - timezoneViewer->resize(QSize(500, 600)); - timezoneViewer->setResizeMode(QQuickWidget::SizeRootObjectToView); - timezoneViewer->setClearColor(Qt::transparent); - timezoneViewer->setAttribute(Qt::WA_AlwaysStackOnTop); + // Timezone + connect(tzonelist, &K4TimeZoneWidget::itemSelectionChanged, this, [this]() { + const QStringList selectedZones(tzonelist->selection()); + setSelectedTimeZone(selectedZones.isEmpty() ? QString() : selectedZones.constFirst()); + }); + tzonesearch->setTreeWidget(tzonelist); } void Dtime::currentZone() { QTimeZone localZone = QTimeZone::systemTimeZone(); - setSelectedTimeZone(localZone.id()); + const auto continentCity = localZone.id().split('/'); + // Use the translation catalog of the digitalclock applet until there is a standard API for city/continent names. + const char *domain = "plasma_applet_org.kde.plasma.digitalclock.mo"; + QString displayName = i18nd(domain, continentCity[0]); + if (continentCity.size() > 1) { + displayName += '/' + i18nd(domain, continentCity[1]); + } + const QString abbreviation = localZone.abbreviation(QDateTime::currentDateTime()); + if (abbreviation.isEmpty()) { + m_local->setText(i18nc("%1 is name of time zone", "Current local time zone: %1", displayName)); + } else { + m_local->setText(i18nc("%1 is name of time zone, %2 is its abbreviation", "Current local time zone: %1 (%2)", displayName, abbreviation)); + } } void Dtime::set_time() @@ -148,6 +155,7 @@ void Dtime::load() // Timezone currentZone(); + setSelectedTimeZone(currentTimeZone); Q_EMIT timeChanged(false); } @@ -163,6 +171,14 @@ void Dtime::setSelectedTimeZone(QString selectedTimeZone) } m_selectedTimeZone = selectedTimeZone; + + const QSignalBlocker blocker(tzonelist); + if (selectedTimeZone.isEmpty()) { + tzonelist->clearSelection(); + } else { + tzonelist->setSelected(selectedTimeZone, true); + } + Q_EMIT selectedTimeZoneChanged(true); } diff --git a/kcms/dateandtime/k4timezonewidget.cpp b/kcms/dateandtime/k4timezonewidget.cpp new file mode 100644 index 00000000..646bf6b3 --- /dev/null +++ b/kcms/dateandtime/k4timezonewidget.cpp @@ -0,0 +1,262 @@ +/* + Copyright (C) 2005, S.R.Haque . + Copyright (C) 2009, David Faure + This file is part of the KDE project + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "k4timezonewidget.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +class Q_DECL_HIDDEN K4TimeZoneWidget::Private +{ +public: + Private() + : itemsCheckable(false) + , singleSelection(true) + { + } + + enum Columns { + CityColumn = 0, + RegionColumn, + CommentColumn, + }; + + enum Roles { + ZoneRole = Qt::UserRole + 0xF3A3CB1, + }; + + bool itemsCheckable; + bool singleSelection; +}; + +static bool localeLessThan(const QString &a, const QString &b) +{ + return QString::localeAwareCompare(a, b) < 0; +} + +K4TimeZoneWidget::K4TimeZoneWidget(QWidget *parent) + : QTreeWidget(parent) + , d(new K4TimeZoneWidget::Private) +{ + // If the user did not provide a timezone database, we'll use the system default. + setRootIsDecorated(false); + setHeaderLabels(QStringList() << i18nc("Define an area in the time zone, like a town area", "Area") << i18nc("Time zone", "Region") << i18n("Comment")); + + const auto locale = icu::Locale(QLocale::system().name().toLatin1()); + UErrorCode error = U_ZERO_ERROR; + std::unique_ptr tzNames(icu::TimeZoneNames::createInstance(locale, error)); + if (!U_SUCCESS(error)) { + qWarning() << "failed to create timezone names" << u_errorName(error); + return; + }; + icu::UnicodeString result; + + const auto timezones = QTimeZone::availableTimeZoneIds(); + + struct TimeZoneInfo { + QTimeZone zone; + QString region; + QString city; + }; + + std::vector zones; + zones.reserve(timezones.size()); + for (const auto &id : timezones) { + const int separator = id.lastIndexOf('/'); + // Make up the localized key that will be used for sorting. + // Example: Asia/Tokyo -> key = "i18n(Tokyo)|i18n(Asia)|Asia/Tokyo" + // The zone name is appended to ensure unicity even with equal translations (#174918) + // since there is no API continent names, we use the translation catalog of the digitalclock applet which contain these + const char *domain = "plasma_applet_org.kde.plasma.digitalclock.mo"; + const QString continent = separator > 0 ? i18nd(domain, id.left(separator)) : QString(); + const icu::UnicodeString &exemplarCity = tzNames->getExemplarLocationName(icu::UnicodeString::fromUTF8(icu::StringPiece(id.data(), id.size())), result); + zones.push_back( + {QTimeZone(id), continent, exemplarCity.isBogus() ? QString::fromLatin1(id) : QString::fromUtf16(exemplarCity.getBuffer(), exemplarCity.length())}); + } + std::sort(zones.begin(), zones.end(), [](const TimeZoneInfo &left, const TimeZoneInfo &right) { + if (auto result = QString::localeAwareCompare(left.city, right.city); result != 0) { + return result < 0; + } + if (auto result = QString::localeAwareCompare(left.region, right.region); result != 0) { + return result < 0; + } + return QString::localeAwareCompare(left.zone.id(), right.zone.id()) < 0; + }); + + for (const auto &zoneInfo : zones) { + const QTimeZone &zone = zoneInfo.zone; + const QString tzName = zone.id(); + QString comment = zone.comment(); + + if (!comment.isEmpty()) { + comment = i18n(comment.toUtf8()); + } + + QTreeWidgetItem *listItem = new QTreeWidgetItem(this); + listItem->setText(Private::CityColumn, zoneInfo.city); + QString countryName = KCountry::fromQLocale(zone.territory()).name(); + if (countryName.isEmpty()) { + countryName = QLocale::territoryToCode(zone.territory()); + } + QString regionLabel = zoneInfo.region; + if (!regionLabel.isEmpty() && !countryName.isEmpty()) { + regionLabel += '/'; + } + regionLabel += countryName; + + listItem->setText(Private::RegionColumn, regionLabel); + listItem->setText(Private::CommentColumn, comment); + listItem->setData(Private::CityColumn, Private::ZoneRole, tzName); // store complete path in custom role + + listItem->setIcon(Private::RegionColumn, QIcon(new KCountryFlagEmojiIconEngine(QLocale::territoryToCode(zone.territory())))); + } +} + +K4TimeZoneWidget::~K4TimeZoneWidget() +{ + delete d; +} + +void K4TimeZoneWidget::setItemsCheckable(bool enable) +{ + d->itemsCheckable = enable; + const int count = topLevelItemCount(); + for (int row = 0; row < count; ++row) { + QTreeWidgetItem *listItem = topLevelItem(row); + listItem->setCheckState(Private::CityColumn, Qt::Unchecked); + } + QTreeWidget::setSelectionMode(QAbstractItemView::NoSelection); +} + +bool K4TimeZoneWidget::itemsCheckable() const +{ + return d->itemsCheckable; +} + +QStringList K4TimeZoneWidget::selection() const +{ + QStringList selection; + + // Loop through all entries. + // Do not use selectedItems() because it skips hidden items, making it + // impossible to use a KTreeWidgetSearchLine. + // There is no QTreeWidgetItemConstIterator, hence the const_cast :/ + QTreeWidgetItemIterator it(const_cast(this), d->itemsCheckable ? QTreeWidgetItemIterator::Checked : QTreeWidgetItemIterator::Selected); + for (; *it; ++it) { + selection.append((*it)->data(Private::CityColumn, Private::ZoneRole).toString()); + } + + return selection; +} + +void K4TimeZoneWidget::setSelected(const QString &zone, bool selected) +{ + bool found = false; + + // The code was using findItems( zone, Qt::MatchExactly, Private::ZoneColumn ) + // previously, but the underlying model only has 3 columns, the "hidden" column + // wasn't available in there. + + if (!d->itemsCheckable) { + // Runtime compatibility for < 4.3 apps, which don't call the setMultiSelection reimplementation. + d->singleSelection = (QTreeWidget::selectionMode() == QAbstractItemView::SingleSelection); + } + + // Loop through all entries. + const int rowCount = model()->rowCount(QModelIndex()); + for (int row = 0; row < rowCount; ++row) { + const QModelIndex index = model()->index(row, Private::CityColumn); + const QString tzName = index.data(Private::ZoneRole).toString(); + if (tzName == zone) { + if (d->singleSelection && selected) { + clearSelection(); + } + + if (d->itemsCheckable) { + QTreeWidgetItem *listItem = itemFromIndex(index); + listItem->setCheckState(Private::CityColumn, selected ? Qt::Checked : Qt::Unchecked); + } else { + selectionModel()->select(index, + selected ? (QItemSelectionModel::Select | QItemSelectionModel::Rows) + : (QItemSelectionModel::Deselect | QItemSelectionModel::Rows)); + } + + // Ensure the selected item is visible as appropriate. + scrollTo(index); + + found = true; + + if (d->singleSelection && selected) { + break; + } + } + } + + if (!found) { + qDebug() << "No such zone: " << zone; + } +} + +void K4TimeZoneWidget::clearSelection() +{ + if (d->itemsCheckable) { + // Un-select all items + const int rowCount = model()->rowCount(QModelIndex()); + for (int row = 0; row < rowCount; ++row) { + const QModelIndex index = model()->index(row, 0); + QTreeWidgetItem *listItem = itemFromIndex(index); + listItem->setCheckState(Private::CityColumn, Qt::Unchecked); + } + } else { + QTreeWidget::clearSelection(); + } +} + +void K4TimeZoneWidget::setSelectionMode(QAbstractItemView::SelectionMode mode) +{ + d->singleSelection = (mode == QAbstractItemView::SingleSelection); + if (!d->itemsCheckable) { + QTreeWidget::setSelectionMode(mode); + } +} + +QAbstractItemView::SelectionMode K4TimeZoneWidget::selectionMode() const +{ + if (d->itemsCheckable) { + return d->singleSelection ? QTreeWidget::SingleSelection : QTreeWidget::MultiSelection; + } else { + return QTreeWidget::selectionMode(); + } +} + +#include "moc_k4timezonewidget.cpp" diff --git a/kcms/dateandtime/k4timezonewidget.h b/kcms/dateandtime/k4timezonewidget.h new file mode 100644 index 00000000..bfeebc17 --- /dev/null +++ b/kcms/dateandtime/k4timezonewidget.h @@ -0,0 +1,139 @@ +/* + Copyright (C) 2005, S.R.Haque . + This file is part of the KDE project + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2, as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KTIMEZONEWIDGET_H +#define KTIMEZONEWIDGET_H + +#include + +class KTimeZones; + +/** + * @brief A time zone selection widget. + * + * \b Detail: + * + * This class provides for selection of one or more time zones. + * + * \b Example: + * + * To use the class to implement a system timezone selection feature: + * \code + * + * // This adds a time zone widget to a dialog. + * m_timezones = new K4TimeZoneWidget(this); + * ... + * \endcode + * + * To use the class to implement a multiple-choice custom time zone selector: + * \code + * + * m_timezones = new K4TimeZoneWidget( this, "Time zones", vcalendarTimezones ); + * m_timezones->setSelectionMode( QTreeView::MultiSelection ); + * ... + * \endcode + * + * \image html ktimezonewidget.png "KDE Time Zone Widget" + * + * @author S.R.Haque + */ + +class K4TimeZoneWidget : public QTreeWidget +{ + Q_OBJECT + Q_PROPERTY(bool itemsCheckable READ itemsCheckable WRITE setItemsCheckable) + Q_PROPERTY(QAbstractItemView::SelectionMode selectionMode READ selectionMode WRITE setSelectionMode) + +public: + /** + * Constructs a time zone selection widget. + * + * @param parent The parent widget. + * @param timeZones The time zone database to use. If 0, the system time zone + * database is used. + */ + explicit K4TimeZoneWidget(QWidget *parent = nullptr); + + /** + * Destroys the time zone selection widget. + */ + ~K4TimeZoneWidget() override; + + /** + * Makes all items show a checkbox, so that the user can select multiple + * timezones by means of checking checkboxes, rather than via multi-selection. + * + * In "items checkable" mode, the selection(), setSelected() and clearSelection() + * methods work on the check states rather than on selecting/unselecting. + * + * @since 4.4 + */ + void setItemsCheckable(bool enable); + /** + * @return true if setItemsCheckable(true) was called. + * @since 4.4 + */ + bool itemsCheckable() const; + + /** + * Allows to select multiple timezones. This is the same as + * setSelectionMode(K4TimeZoneWidget::MultiSelection) normally, + * but in "items checkable" mode, this is rather about allowing to + * check multiple items. In that case, the actual QTreeWidget selection + * mode remains unchanged. + * @since 4.4 + */ + void setSelectionMode(QAbstractItemView::SelectionMode mode); + + /** + * @return the selection mode set by setSelectionMode(). + * @since 4.4 + */ + QAbstractItemView::SelectionMode selectionMode() const; + + /** + * Returns the currently selected time zones. See QTreeView::selectionChanged(). + * + * @return a list of time zone names, in the format used by the database + * supplied to the {@link K4TimeZoneWidget() } constructor. + */ + QStringList selection() const; + + /** + * Select/deselect the named time zone. + * + * @param zone The time zone name to be selected. Ignored if not recognized! + * @param selected The new selection state. + */ + void setSelected(const QString &zone, bool selected); + + /** + * Unselect all timezones. + * This is the same as QTreeWidget::clearSelection, except in checkable items mode, + * where items are all unchecked. + * The overload is @since 4.4. + */ + void clearSelection(); + +private: + class Private; + Private *const d; +}; + +#endif diff --git a/kcms/dateandtime/ui/CMakeLists.txt b/kcms/dateandtime/ui/CMakeLists.txt deleted file mode 100644 index 32fa5c49..00000000 --- a/kcms/dateandtime/ui/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -qt_add_resources( - kcm_clock "timezone_files" - PREFIX "kcm/kcm_clock" - FILES "main.qml" -) diff --git a/kcms/dateandtime/ui/main.qml b/kcms/dateandtime/ui/main.qml deleted file mode 100644 index 47d89afb..00000000 --- a/kcms/dateandtime/ui/main.qml +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 Niccolò Venerandi - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -import QtQuick - -import org.kde.plasma.workspace.timezoneselector as TimeZone - -TimeZone.TimezoneSelector { - selectedTimeZone: DTime.selectedTimeZone - - Connections { - target: DTime - onSelectedTimeZoneChanged: { - selectedTimeZone = DTime.selectedTimeZone - } - } - - onSelectedTimeZoneChanged: { - DTime.selectedTimeZone = selectedTimeZone - } -} diff --git a/po/ru/kcmkclock.po b/po/ru/kcmkclock.po index e4f975ea..155bff49 100644 --- a/po/ru/kcmkclock.po +++ b/po/ru/kcmkclock.po @@ -65,6 +65,53 @@ msgstr "Здесь вы можете изменить системную дат msgid "Time Zone" msgstr "Часовой пояс" +#. i18n: ectx: property (text), widget (QLabel, label) +#: dateandtime.ui +#, kde-format +msgid "To change the local time zone, select your area from the list below." +msgstr "Для изменения часового пояса выберите свой регион из списка." + +#. i18n: ectx: property (text), widget (QLabel, m_local) +#: dateandtime.ui +#, kde-format +msgid "Current local time zone:" +msgstr "Текущий часовой пояс:" + +#. i18n: ectx: property (placeholderText), widget (KTreeWidgetSearchLine, tzonesearch) +#: dateandtime.ui +#, kde-format +msgid "Search…" +msgstr "Введите местоположение..." + +#: dtime.cpp +#, kde-format +msgctxt "%1 is name of time zone" +msgid "Current local time zone: %1" +msgstr "Текущий часовой пояс: %1" + +#: dtime.cpp +#, kde-format +msgctxt "%1 is name of time zone, %2 is its abbreviation" +msgid "Current local time zone: %1 (%2)" +msgstr "Текущий часовой пояс: %1 (%2)" + +#: k4timezonewidget.cpp +#, kde-format +msgctxt "Define an area in the time zone, like a town area" +msgid "Area" +msgstr "Область" + +#: k4timezonewidget.cpp +#, kde-format +msgctxt "Time zone" +msgid "Region" +msgstr "Регион" + +#: k4timezonewidget.cpp +#, kde-format +msgid "Comment" +msgstr "Комментарий" + #: dtime.cpp:71 #, kde-format msgid "" @@ -142,34 +189,6 @@ msgstr "Не удалось сменить часовой пояс" #~ msgid "Unable to authenticate/execute the action: %1, %2" #~ msgstr "Не удалось авторизоваться и выполнить действие: %1, %2" -#~ msgid "To change the local time zone, select your area from the list below." -#~ msgstr "Для изменения часового пояса выберите свой регион из списка." - -#~ msgid "Current local time zone:" -#~ msgstr "Текущий часовой пояс:" - -#~ msgid "Search…" -#~ msgstr "Введите местоположение..." - -#~ msgctxt "%1 is name of time zone" -#~ msgid "Current local time zone: %1" -#~ msgstr "Текущий часовой пояс: %1" - -#~ msgctxt "%1 is name of time zone, %2 is its abbreviation" -#~ msgid "Current local time zone: %1 (%2)" -#~ msgstr "Текущий часовой пояс: %1 (%2)" - -#~ msgctxt "Define an area in the time zone, like a town area" -#~ msgid "Area" -#~ msgstr "Область" - -#~ msgctxt "Time zone" -#~ msgid "Region" -#~ msgstr "Регион" - -#~ msgid "Comment" -#~ msgstr "Комментарий" - #~ msgid "KDE Clock Control Module" #~ msgstr "Модуль настройки часов KDE" -- 2.50.1