mirror of
https://invent.kde.org/plasma/plasma-desktop.git
synced 2026-11-03 14:49:44 -09:00
kcms/keyboard: Refactor and fix LayoutDialog
The list had a few problems, notably an erratic sorting that randomly change during and after searching for a layout. While at it, refactor some code and port the models to C++.
This commit is contained in:
parent
c7cf9cec98
commit
dc7b9473f7
11 changed files with 232 additions and 152 deletions
|
|
@ -19,6 +19,7 @@ set( kcm_keyboard_PART_SRCS
|
|||
xkboptionsmodel.cpp
|
||||
userlayoutmodel.cpp
|
||||
layoutsearchmodel.cpp
|
||||
variantsmodel.cpp
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(kcm_keyboard_PART_SRCS
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ QVariant LayoutModel::data(const QModelIndex &index, int role) const
|
|||
|
||||
const auto &layout = m_data.at(index.row());
|
||||
|
||||
if (role == Roles::DescripionRole || role == Qt::DisplayRole) {
|
||||
if (role == Roles::DescriptionRole || role == Qt::DisplayRole) {
|
||||
return layout.description;
|
||||
} else if (role == Roles::ShortNameRole) {
|
||||
return layout.name;
|
||||
|
|
@ -54,7 +54,7 @@ QHash<int, QByteArray> LayoutModel::roleNames() const
|
|||
return {
|
||||
{Qt::DisplayRole, QByteArrayLiteral("display")},
|
||||
{Roles::ShortNameRole, QByteArrayLiteral("shortName")},
|
||||
{Roles::DescripionRole, QByteArrayLiteral("description")},
|
||||
{Roles::DescriptionRole, QByteArrayLiteral("description")},
|
||||
{Roles::VariantNameRole, QByteArrayLiteral("variantName")},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public:
|
|||
QHash<int, QByteArray> roleNames() const override;
|
||||
enum Roles {
|
||||
ShortNameRole = Qt::UserRole + 1,
|
||||
DescripionRole,
|
||||
DescriptionRole,
|
||||
VariantNameRole,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2025 Bharadwaj Raju <bharadwaj.raju777@protonmail.com>
|
||||
|
||||
SPDX-FileCopyrightText: 2026 Tobias Fella <tobias.fella@kde.org>
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "layoutsearchmodel.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QStringLiteral>
|
||||
|
||||
#include <KFuzzyMatcher>
|
||||
|
||||
#include "layoutmodel.h"
|
||||
|
|
@ -20,53 +15,78 @@ using namespace Qt::Literals::StringLiterals;
|
|||
LayoutSearchModel::LayoutSearchModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
connect(this, &QSortFilterProxyModel::sourceModelChanged, this, [this]() {
|
||||
const auto originalRoles = sourceModel()->roleNames().keys();
|
||||
m_searchScoreRole = *std::max_element(originalRoles.begin(), originalRoles.end()) + 1;
|
||||
});
|
||||
setSortRole(LayoutModel::Roles::DescriptionRole);
|
||||
sort(0);
|
||||
}
|
||||
|
||||
void LayoutSearchModel::setSearchString(QStringView searchString)
|
||||
QString LayoutSearchModel::searchString() const
|
||||
{
|
||||
beginResetModel();
|
||||
m_searchString = searchString.toString();
|
||||
endResetModel();
|
||||
return m_searchString;
|
||||
}
|
||||
|
||||
void LayoutSearchModel::setSearchString(const QString &searchString)
|
||||
{
|
||||
if (searchString == m_searchString) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginFilterChange();
|
||||
m_searchString = searchString;
|
||||
endFilterChange();
|
||||
|
||||
sort(-1);
|
||||
sort(0);
|
||||
|
||||
Q_EMIT searchStringChanged();
|
||||
}
|
||||
|
||||
QString LayoutSearchModel::getFullName(const QModelIndex &idx) const
|
||||
{
|
||||
const auto shortName = sourceModel()->data(idx, LayoutModel::Roles::ShortNameRole).toString();
|
||||
const auto description = sourceModel()->data(idx, LayoutModel::Roles::DescripionRole).toString();
|
||||
const auto variantName = sourceModel()->data(idx, LayoutModel::Roles::VariantNameRole).toString();
|
||||
const auto shortName = idx.data(LayoutModel::Roles::ShortNameRole).toString();
|
||||
const auto description = idx.data(LayoutModel::Roles::DescriptionRole).toString();
|
||||
const auto variantName = idx.data(LayoutModel::Roles::VariantNameRole).toString();
|
||||
|
||||
return shortName + " - "_L1 + description + " - "_L1 + variantName;
|
||||
}
|
||||
|
||||
QVariant LayoutSearchModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
if (!idx.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (role == m_searchScoreRole)
|
||||
return QVariant(KFuzzyMatcher::match(m_searchString, getFullName(idx)).score);
|
||||
|
||||
return sourceModel()->data(idx, role);
|
||||
}
|
||||
|
||||
bool LayoutSearchModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
return true;
|
||||
const auto idx = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
const auto res = KFuzzyMatcher::match(m_searchString, getFullName(idx));
|
||||
return res.matched;
|
||||
const auto index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
|
||||
if (!index.data(LayoutModel::Roles::VariantNameRole).toString().trimmed().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> LayoutSearchModel::roleNames() const
|
||||
if (m_searchString.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getFullName(index).contains(m_searchString, Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto shortName = index.data(LayoutModel::ShortNameRole).toString();
|
||||
|
||||
for (auto i = 0; i < sourceModel()->rowCount(); i++) {
|
||||
const auto sourceIndex = sourceModel()->index(i, 0, sourceParent);
|
||||
const auto sourceShortName = sourceIndex.data(LayoutModel::ShortNameRole).toString();
|
||||
if (sourceShortName == shortName && getFullName(sourceIndex).contains(m_searchString, Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayoutSearchModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
QHash<int, QByteArray> newRoles(sourceModel()->roleNames());
|
||||
newRoles.insert(m_searchScoreRole, "searchScore"_ba);
|
||||
return newRoles;
|
||||
auto leftScore = KFuzzyMatcher::match(m_searchString, getFullName(left)).score;
|
||||
auto rightScore = KFuzzyMatcher::match(m_searchString, getFullName(right)).score;
|
||||
|
||||
if (leftScore == rightScore) {
|
||||
return QSortFilterProxyModel::lessThan(left, right);
|
||||
}
|
||||
|
||||
return leftScore > rightScore;
|
||||
}
|
||||
|
||||
#include "moc_layoutsearchmodel.cpp"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2025 Bharadwaj Raju <bharadwaj.raju777@protonmail.com>
|
||||
|
||||
SPDX-FileCopyrightText: 2026 Tobias Fella <tobias.fella@kde.org>
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
|
|
@ -12,27 +12,23 @@ class LayoutSearchModel : public QSortFilterProxyModel
|
|||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LayoutSearchModel(QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE QString searchString() const
|
||||
{
|
||||
return m_searchString;
|
||||
};
|
||||
Q_INVOKABLE void setSearchString(QStringView searchString);
|
||||
|
||||
Q_PROPERTY(QString searchString READ searchString WRITE setSearchString NOTIFY searchStringChanged)
|
||||
|
||||
signals:
|
||||
public:
|
||||
explicit LayoutSearchModel(QObject *parent = nullptr);
|
||||
|
||||
QString searchString() const;
|
||||
void setSearchString(const QString &searchString);
|
||||
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void searchStringChanged();
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
QVariant data(const QModelIndex &idx, int role) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
QString m_searchString;
|
||||
QString m_searchString = QStringLiteral("");
|
||||
QString getFullName(const QModelIndex &idx) const;
|
||||
int m_searchScoreRole;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "flags.h"
|
||||
#include "layoutsearchmodel.h"
|
||||
#include "variantsmodel.h"
|
||||
|
||||
struct FlagsForeign {
|
||||
Q_GADGET
|
||||
|
|
@ -18,3 +19,9 @@ struct LayoutSearchModelForeign {
|
|||
QML_NAMED_ELEMENT(LayoutSearchModel)
|
||||
QML_FOREIGN(LayoutSearchModel)
|
||||
};
|
||||
|
||||
struct VariantsModelForeign {
|
||||
Q_GADGET
|
||||
QML_NAMED_ELEMENT(VariantsModel)
|
||||
QML_FOREIGN(VariantsModel)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2024 Evgeny Chesnokov <echesnokov@astralinux.ru>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
|
||||
import org.kde.kitemmodels as KItemModels
|
||||
|
||||
KItemModels.KSortFilterProxyModel {
|
||||
id: layoutsProxy
|
||||
sourceModel: layoutSearchProxy
|
||||
sortRoleName: "searchScore"
|
||||
sortOrder: Qt.DescendingOrder
|
||||
|
||||
filterRowCallback: function (row, parent): bool {
|
||||
const modelIndex = sourceModel.index(row, 0, parent);
|
||||
const currentVariantName = sourceModel.data(modelIndex, KItemModels.KRoleNames.role("variantName"));
|
||||
const description = sourceModel.data(modelIndex, KItemModels.KRoleNames.role("description"));
|
||||
|
||||
if (currentVariantName !== '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (searchField.text.length > 0) {
|
||||
const score = sourceModel.data(modelIndex, KItemModels.KRoleNames.role("searchScore"));
|
||||
if (score !== 0) {
|
||||
return true;
|
||||
}
|
||||
const shortNameRole = KItemModels.KRoleNames.role("shortName");
|
||||
const currentName = sourceModel.data(modelIndex, shortNameRole);
|
||||
const searchScoreRole = KItemModels.KRoleNames.role("searchScore");
|
||||
for (let i = 0; i < sourceModel.rowCount(); i++) {
|
||||
const index = sourceModel.index(i, 0, parent);
|
||||
const name = sourceModel.data(index, shortNameRole);
|
||||
const variantSearchScore = sourceModel.data(index, searchScoreRole);
|
||||
if (name === currentName && variantSearchScore > 100) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2024 Evgeny Chesnokov <echesnokov@astralinux.ru>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
|
||||
import org.kde.kitemmodels as KItemModels
|
||||
|
||||
KItemModels.KSortFilterProxyModel {
|
||||
id: variantProxy
|
||||
sourceModel: layoutSearchProxy
|
||||
sortRoleName: "searchScore"
|
||||
sortOrder: Qt.DescendingOrder
|
||||
|
||||
filterRowCallback: function (row, parent): bool {
|
||||
if (!layoutsView.currentItem) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const modelIndex = sourceModel.index(row, 0, parent);
|
||||
const currentName = sourceModel.data(modelIndex, KItemModels.KRoleNames.role("shortName"));
|
||||
const selectedName = layoutsView.currentItem.shortName;
|
||||
|
||||
if (currentName !== selectedName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (searchField.text.length > 0) {
|
||||
const searchScore = sourceModel.data(modelIndex, KItemModels.KRoleNames.role("searchScore"));
|
||||
return searchScore > 100;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -47,18 +47,15 @@ Kirigami.Dialog {
|
|||
}
|
||||
}
|
||||
|
||||
KCMKeyboard.LayoutSearchModel {
|
||||
id: layoutSearchProxy
|
||||
sourceModel: KCMKeyboard.LayoutModel {}
|
||||
searchString: ""
|
||||
KCMKeyboard.LayoutModel {
|
||||
id: layoutModel
|
||||
}
|
||||
|
||||
KeyboardLayoutsModel {
|
||||
id: layoutsProxy
|
||||
}
|
||||
|
||||
KeyboardVariantsModel {
|
||||
KCMKeyboard.VariantsModel {
|
||||
id: variantProxy
|
||||
searchString: searchField.text
|
||||
shortName: layoutsView.currentItem?.shortName ?? ""
|
||||
sourceModel: layoutModel
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
|
|
@ -67,8 +64,10 @@ Kirigami.Dialog {
|
|||
Kirigami.SearchField {
|
||||
id: searchField
|
||||
Layout.fillWidth: true
|
||||
onAccepted: {
|
||||
layoutSearchProxy.searchString = searchField.text.trim();
|
||||
|
||||
onTextChanged: {
|
||||
layoutsView.currentIndex = 0;
|
||||
variantView.currentIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -84,12 +83,13 @@ Kirigami.Dialog {
|
|||
contentItem: ListView {
|
||||
id: layoutsView
|
||||
currentIndex: 0
|
||||
model: layoutsProxy
|
||||
model: KCMKeyboard.LayoutSearchModel {
|
||||
sourceModel: layoutModel
|
||||
searchString: searchField.text
|
||||
}
|
||||
delegate: LayoutDelegate {}
|
||||
keyNavigationEnabled: true
|
||||
activeFocusOnTab: true
|
||||
|
||||
onCurrentItemChanged: variantProxy.invalidateFilter()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
103
kcms/keyboard/variantsmodel.cpp
Normal file
103
kcms/keyboard/variantsmodel.cpp
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// SPDX-FileCopyrightText: 2026 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "variantsmodel.h"
|
||||
|
||||
#include <KFuzzyMatcher>
|
||||
|
||||
#include "layoutmodel.h"
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
VariantsModel::VariantsModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
setSortRole(LayoutModel::DescriptionRole);
|
||||
sort(0);
|
||||
}
|
||||
|
||||
QString VariantsModel::searchString() const
|
||||
{
|
||||
return m_searchString;
|
||||
}
|
||||
|
||||
void VariantsModel::setSearchString(const QString &searchString)
|
||||
{
|
||||
if (searchString == m_searchString) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginFilterChange();
|
||||
m_searchString = searchString;
|
||||
endFilterChange();
|
||||
|
||||
sort(-1);
|
||||
sort(0);
|
||||
|
||||
Q_EMIT searchStringChanged();
|
||||
}
|
||||
|
||||
QString VariantsModel::shortName() const
|
||||
{
|
||||
return m_shortName;
|
||||
}
|
||||
|
||||
void VariantsModel::setShortName(const QString &shortName)
|
||||
{
|
||||
if (shortName == m_shortName) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginFilterChange();
|
||||
m_shortName = shortName;
|
||||
endFilterChange();
|
||||
|
||||
Q_EMIT shortNameChanged();
|
||||
}
|
||||
|
||||
QString VariantsModel::getFullName(const QModelIndex &idx) const
|
||||
{
|
||||
const auto shortName = idx.data(LayoutModel::Roles::ShortNameRole).toString();
|
||||
const auto description = idx.data(LayoutModel::Roles::DescriptionRole).toString();
|
||||
const auto variantName = idx.data(LayoutModel::Roles::VariantNameRole).toString();
|
||||
|
||||
return shortName + " - "_L1 + description + " - "_L1 + variantName;
|
||||
}
|
||||
|
||||
bool VariantsModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
if (m_shortName.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
if (index.data(LayoutModel::ShortNameRole).toString() != m_shortName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_searchString.isEmpty()) {
|
||||
return getFullName(index).contains(m_searchString, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VariantsModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
auto leftScore = KFuzzyMatcher::match(m_searchString, getFullName(left)).score;
|
||||
auto rightScore = KFuzzyMatcher::match(m_searchString, getFullName(right)).score;
|
||||
|
||||
if (leftScore != rightScore) {
|
||||
return leftScore > rightScore;
|
||||
}
|
||||
|
||||
const auto leftIsBase = left.data(LayoutModel::VariantNameRole).toString().isEmpty();
|
||||
const auto rightIsBase = right.data(LayoutModel::VariantNameRole).toString().isEmpty();
|
||||
if (leftIsBase != rightIsBase) {
|
||||
return leftIsBase;
|
||||
}
|
||||
|
||||
return QSortFilterProxyModel::lessThan(left, right);
|
||||
}
|
||||
|
||||
#include "moc_variantsmodel.cpp"
|
||||
37
kcms/keyboard/variantsmodel.h
Normal file
37
kcms/keyboard/variantsmodel.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// SPDX-FileCopyrightText: 2026 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class VariantsModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString searchString READ searchString WRITE setSearchString NOTIFY searchStringChanged)
|
||||
Q_PROPERTY(QString shortName READ shortName WRITE setShortName NOTIFY shortNameChanged)
|
||||
|
||||
public:
|
||||
explicit VariantsModel(QObject *parent = nullptr);
|
||||
|
||||
QString searchString() const;
|
||||
void setSearchString(const QString &searchString);
|
||||
|
||||
QString shortName() const;
|
||||
void setShortName(const QString &shortName);
|
||||
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void searchStringChanged();
|
||||
void shortNameChanged();
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
|
||||
private:
|
||||
QString m_searchString = QStringLiteral("");
|
||||
QString getFullName(const QModelIndex &idx) const;
|
||||
QString m_shortName;
|
||||
};
|
||||
Loading…
Reference in a new issue