[plasma/plasma-setup] modules: Improve language search in Plasma Setup
Kristen McWilliam <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit b5a0a068ff74f4c69cfcc664dd454fc19312d53e by Kristen McWilliam, on behalf of Tiziano Gaia. Committed on 21/07/2026 at 17:23. Pushed by merritt into branch 'master'. Improve language search in Plasma Setup Use QSortFilterProxyModel for language filtering M +21 -28 modules/language/contents/ui/main.qml M +1 -0 modules/languageutil/CMakeLists.txt A +56 -0 modules/languageutil/languagesortfilterproxymodel.cpp [License: LGPL(v2.1+)] A +32 -0 modules/languageutil/languagesortfilterproxymodel.h [License: LGPL(v2.1+)] M +28 -0 modules/languageutil/languageutil.cpp M +28 -0 modules/languageutil/languageutil.h https://invent.kde.org/plasma/plasma-setup/-/commit/b5a0a068ff74f4c69cfcc664dd454fc19312d53e diff --git a/modules/language/contents/ui/main.qml b/modules/language/contents/ui/main.qml index 3d7f767..a52eaa0 100644 --- a/modules/language/contents/ui/main.qml +++ b/modules/language/contents/ui/main.qml @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2025 Kristen McWilliam <[email protected]> +// SPDX-FileCopyrightText: 2026 Tiziano Gaia <[email protected]> // SPDX-License-Identifier: LGPL-2.1-or-later pragma ComponentBehavior: Bound @@ -49,10 +50,9 @@ PlasmaSetupComponents.SetupModule { id: searchField Layout.fillWidth: true placeholderText: i18n("Search languages…") - property string filterString: "" onTextChanged: { - filterString = text.toLowerCase(); + Language.LanguageUtil.languageFilter = text; } } @@ -69,45 +69,35 @@ PlasmaSetupComponents.SetupModule { ListView { id: languageListView clip: true - model: Language.LanguageUtil.availableLanguages - - // Filter languages based on search text - function matchesFilter(language) { - if (!searchField.filterString) { - return true; - } - - // Get language name from locale code - const localeName = Qt.locale(language).nativeLanguageName; - return language.toLowerCase().includes(searchField.filterString) || localeName.toLowerCase().includes(searchField.filterString); - } + model: Language.LanguageUtil.languageModel currentIndex: -1 // Ensure focus is not on the listview delegate: RadioDelegate { - required property string modelData + required property string languageCode - // Show and hide based on filter - readonly property bool matchesFilter: languageListView.matchesFilter(modelData) - height: matchesFilter ? implicitHeight : 0 width: ListView.view.width - visible: matchesFilter // Get language name from locale code (e.g., "en_US" -> "English (United States)") text: { - const locale = Qt.locale(modelData); + const locale = Qt.locale(languageCode); const localeName = locale.nativeLanguageName; + // First letter to uppercase - return localeName.charAt(0).toUpperCase() + localeName.slice(1) + " (" + locale.nativeCountryName + ")"; + return localeName.charAt(0).toUpperCase() + + localeName.slice(1) + + " (" + + locale.nativeCountryName + + ")"; } - checked: Language.LanguageUtil.currentLanguage === modelData + checked: Language.LanguageUtil.currentLanguage === languageCode onToggled: { - if (checked && modelData !== Language.LanguageUtil.currentLanguage) { - Language.LanguageUtil.currentLanguage = modelData; + if (checked && languageCode !== Language.LanguageUtil.currentLanguage) { + Language.LanguageUtil.currentLanguage = languageCode; Language.LanguageUtil.applyLanguage(); - checked = Qt.binding(() => Language.LanguageUtil.currentLanguage === modelData); + checked = Qt.binding(() => Language.LanguageUtil.currentLanguage === languageCode); } } } @@ -115,10 +105,13 @@ PlasmaSetupComponents.SetupModule { function scrollToCurrentLanguage() { // Find the index of the current language const currentLang = Language.LanguageUtil.currentLanguage; - for (let i = 0; i < model.length; i++) { - if (model[i] === currentLang) { + + for (let i = 0; i < languageListView.count; i++) { + const item = languageListView.itemAtIndex(i); + + if (item && item.languageCode === currentLang) { // Position the view at the current language with some offset - positionViewAtIndex(i, ListView.Center); + languageListView.positionViewAtIndex(i, ListView.Center); break; } } diff --git a/modules/languageutil/CMakeLists.txt b/modules/languageutil/CMakeLists.txt index 73fda76..af807bf 100644 --- a/modules/languageutil/CMakeLists.txt +++ b/modules/languageutil/CMakeLists.txt @@ -16,6 +16,7 @@ ecm_add_qml_module(plasmasetup_languageutil GENERATE_PLUGIN_SOURCE SOURCES languageutil.cpp + languagesortfilterproxymodel.cpp ${logging_SRCS} ) diff --git a/modules/languageutil/languagesortfilterproxymodel.cpp b/modules/languageutil/languagesortfilterproxymodel.cpp new file mode 100644 index 0000000..fc4f1b3 --- /dev/null +++ b/modules/languageutil/languagesortfilterproxymodel.cpp @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2026 Tiziano Gaia <[email protected]> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "languagesortfilterproxymodel.h" + +#include <QLocale> + +LanguageSortFilterProxyModel::LanguageSortFilterProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} + +void LanguageSortFilterProxyModel::setFilterString(const QString &filter) +{ + if (m_filterString == filter) { + return; + } + + beginFilterChange(); + m_filterString = filter; + endFilterChange(); +} + +bool LanguageSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + if (m_filterString.isEmpty()) { + return true; + } + + const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + const QString language = sourceModel()->data(index, Qt::DisplayRole).toString(); + + const QLocale locale(language); + + const QString nativeName = locale.nativeLanguageName(); + const QString englishName = QLocale::languageToString(locale.language()); + + return language.contains(m_filterString, Qt::CaseInsensitive) || nativeName.contains(m_filterString, Qt::CaseInsensitive) + || englishName.contains(m_filterString, Qt::CaseInsensitive); +} + +QHash<int, QByteArray> LanguageSortFilterProxyModel::roleNames() const +{ + auto roles = QSortFilterProxyModel::roleNames(); + roles[Qt::UserRole + 1] = "languageCode"; + return roles; +} + +QVariant LanguageSortFilterProxyModel::data(const QModelIndex &index, int role) const +{ + if (role == Qt::UserRole + 1) { + return sourceModel()->data(mapToSource(index), Qt::DisplayRole); + } + + return QSortFilterProxyModel::data(index, role); +} diff --git a/modules/languageutil/languagesortfilterproxymodel.h b/modules/languageutil/languagesortfilterproxymodel.h new file mode 100644 index 0000000..0089b98 --- /dev/null +++ b/modules/languageutil/languagesortfilterproxymodel.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2026 Tiziano Gaia <[email protected]> +// SPDX-License-Identifier: LGPL-2.1-or-later + +#pragma once + +#include <QSortFilterProxyModel> +#include <QString> + +class QModelIndex; +class QVariant; + +/** + * A proxy model that filters the list of available languages + * by locale code, native language name, and English language name. + */ +class LanguageSortFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + explicit LanguageSortFilterProxyModel(QObject *parent = nullptr); + + void setFilterString(const QString &filter); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + QHash<int, QByteArray> roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +private: + QString m_filterString; +}; diff --git a/modules/languageutil/languageutil.cpp b/modules/languageutil/languageutil.cpp index 9d3f01e..6bcd1b3 100644 --- a/modules/languageutil/languageutil.cpp +++ b/modules/languageutil/languageutil.cpp @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2025 Kristen McWilliam <[email protected]> +// SPDX-FileCopyrightText: 2026 Tiziano Gaia <[email protected]> // SPDX-License-Identifier: LGPL-2.1-or-later #include "languageutil.h" @@ -9,12 +10,17 @@ #include <QCoreApplication> #include <QDBusConnection> #include <QDBusReply> +#include <QLocale> #include <QTimer> LanguageUtil::LanguageUtil(QObject *parent) : QObject(parent) { loadAvailableLanguages(); + + m_languageModel.setStringList(m_availableLanguages); + m_languageProxyModel.setSourceModel(&m_languageModel); + m_currentLanguage = QLocale::system().name(); qCInfo(PlasmaSetupLanguageUtil) << "System language detected as:" << m_currentLanguage; overrideInitialLanguageIfNeeded(); @@ -25,6 +31,28 @@ QStringList LanguageUtil::availableLanguages() const return m_availableLanguages; } +QString LanguageUtil::languageFilter() const +{ + return m_languageFilter; +} + +QAbstractItemModel *LanguageUtil::languageModel() +{ + return &m_languageProxyModel; +} + +void LanguageUtil::setLanguageFilter(const QString &filter) +{ + if (m_languageFilter == filter) { + return; + } + + m_languageFilter = filter; + m_languageProxyModel.setFilterString(filter); + + Q_EMIT languageFilterChanged(); +} + QString LanguageUtil::currentLanguage() const { return m_currentLanguage; diff --git a/modules/languageutil/languageutil.h b/modules/languageutil/languageutil.h index 51c967f..bbf4954 100644 --- a/modules/languageutil/languageutil.h +++ b/modules/languageutil/languageutil.h @@ -1,10 +1,14 @@ // SPDX-FileCopyrightText: 2025 Kristen McWilliam <[email protected]> +// SPDX-FileCopyrightText: 2026 Tiziano Gaia <[email protected]> // SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include <QObject> #include <QQmlEngine> +#include <QStringListModel> + +#include "languagesortfilterproxymodel.h" /** * Handles language choice. @@ -28,6 +32,22 @@ class LanguageUtil : public QObject */ Q_PROPERTY(QStringList availableLanguages READ availableLanguages NOTIFY availableLanguagesChanged) + /** + * Model containing the available languages. + * + * This model supports filtering through languageFilter and is exposed + * directly to QML views that display the available language choices. + */ + Q_PROPERTY(QAbstractItemModel *languageModel READ languageModel CONSTANT) + + /** + * Search filter applied to the language model. + * + * Updating this value filters languages by their code, native name, + * or English name. + */ + Q_PROPERTY(QString languageFilter READ languageFilter WRITE setLanguageFilter NOTIFY languageFilterChanged) + /** * The language code of the currently selected language. */ @@ -37,6 +57,10 @@ public: explicit LanguageUtil(QObject *parent = nullptr); QStringList availableLanguages() const; + QAbstractItemModel *languageModel(); + QString languageFilter() const; + void setLanguageFilter(const QString &filter); + QString currentLanguage() const; void setCurrentLanguage(const QString &language); @@ -47,6 +71,7 @@ public: Q_SIGNALS: void availableLanguagesChanged(); + void languageFilterChanged(); void currentLanguageChanged(); void initialLanguageOverrideApplied(); @@ -84,5 +109,8 @@ private: void overrideInitialLanguageIfNeeded(); QStringList m_availableLanguages; + QStringListModel m_languageModel; + LanguageSortFilterProxyModel m_languageProxyModel; + QString m_languageFilter; QString m_currentLanguage; };