feat: Add RegExpRole proxy role type

Add support for multiple roles for one proxy role.
This commit is contained in:
Grecko 2018-08-08 01:32:33 +02:00
parent d2772bd6e4
commit 3a55ea0d8c
21 changed files with 326 additions and 73 deletions

View File

@ -23,7 +23,9 @@ HEADERS += $$PWD/qqmlsortfilterproxymodel.h \
$$PWD/proxyroles/proxyrolecontainer.h \
$$PWD/proxyroles/joinrole.h \
$$PWD/proxyroles/switchrole.h \
$$PWD/proxyroles/expressionrole.h
$$PWD/proxyroles/expressionrole.h \
$$PWD/proxyroles/singlerole.h \
$$PWD/proxyroles/regexprole.h
SOURCES += $$PWD/qqmlsortfilterproxymodel.cpp \
$$PWD/filters/filter.cpp \
@ -49,4 +51,6 @@ SOURCES += $$PWD/qqmlsortfilterproxymodel.cpp \
$$PWD/proxyroles/joinrole.cpp \
$$PWD/proxyroles/switchrole.cpp \
$$PWD/proxyroles/expressionrole.cpp \
$$PWD/proxyroles/proxyrolesqmltypes.cpp
$$PWD/proxyroles/proxyrolesqmltypes.cpp \
$$PWD/proxyroles/singlerole.cpp \
$$PWD/proxyroles/regexprole.cpp

View File

@ -6,7 +6,7 @@ namespace qqsfpm {
/*!
\qmltype ExpressionRole
\inherits ProxyRole
\inherits SingleRole
\inqmlmodule SortFilterProxyModel
\brief A custom role computed from a javascrip expression

View File

@ -1,20 +1,20 @@
#ifndef EXPRESSIONROLE_H
#define EXPRESSIONROLE_H
#include "proxyrole.h"
#include "singlerole.h"
#include <QQmlScriptString>
class QQmlExpression;
namespace qqsfpm {
class ExpressionRole : public ProxyRole
class ExpressionRole : public SingleRole
{
Q_OBJECT
Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged)
public:
using ProxyRole::ProxyRole;
using SingleRole::SingleRole;
const QQmlScriptString& expression() const;
void setExpression(const QQmlScriptString& scriptString);

View File

@ -5,7 +5,7 @@ namespace qqsfpm {
/*!
\qmltype JoinRole
\inherits ProxyRole
\inherits SingleRole
\inqmlmodule SortFilterProxyModel
\brief a role made from concatenating other roles

View File

@ -1,18 +1,18 @@
#ifndef JOINROLE_H
#define JOINROLE_H
#include "proxyrole.h"
#include "singlerole.h"
namespace qqsfpm {
class JoinRole : public ProxyRole
class JoinRole : public SingleRole
{
Q_OBJECT
Q_PROPERTY(QStringList roleNames READ roleNames WRITE setRoleNames NOTIFY roleNamesChanged)
Q_PROPERTY(QString separator READ separator WRITE setSeparator NOTIFY separatorChanged)
public:
using ProxyRole::ProxyRole;
using SingleRole::SingleRole;
QStringList roleNames() const;
void setRoleNames(const QStringList& roleNames);

View File

@ -20,38 +20,15 @@ namespace qqsfpm {
available across all the other proxy role types that inherit from it.
Attempting to use the ProxyRole type directly will result in an error.
*/
ProxyRole::ProxyRole(QObject *parent) : QObject(parent)
{
}
/*!
\qmlproperty string ProxyRole::name
This property holds the role name of the proxy role.
*/
const QString& ProxyRole::name() const
{
return m_name;
}
void ProxyRole::setName(const QString& name)
{
if (m_name == name)
return;
Q_EMIT nameAboutToBeChanged();
m_name = name;
Q_EMIT nameChanged();
}
QVariant ProxyRole::roleData(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel)
QVariant ProxyRole::roleData(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel, const QString &name)
{
if (m_mutex.tryLock()) {
QVariant result = data(sourceIndex, proxyModel);
QVariant result = data(sourceIndex, proxyModel, name);
m_mutex.unlock();
return result;
} else {
return QVariant{};
return {};
}
}
@ -65,11 +42,4 @@ void ProxyRole::invalidate()
Q_EMIT invalidated();
}
}

View File

@ -11,29 +11,27 @@ class QQmlSortFilterProxyModel;
class ProxyRole : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:
explicit ProxyRole(QObject *parent = nullptr);
using QObject::QObject;
virtual ~ProxyRole() = default;
const QString& name() const;
void setName(const QString& name);
QVariant roleData(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel);
QVariant roleData(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel, const QString& name);
virtual void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel);
virtual QStringList names() = 0;
protected:
void invalidate();
Q_SIGNALS:
void nameAboutToBeChanged();
void nameChanged();
void invalidated();
void namesAboutToBeChanged();
void namesChanged();
private:
virtual QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) = 0;
virtual QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel, const QString& name) = 0;
QString m_name;
QMutex m_mutex;
};

View File

@ -2,6 +2,7 @@
#include "joinrole.h"
#include "switchrole.h"
#include "expressionrole.h"
#include "regexprole.h"
#include <QQmlEngine>
#include <QCoreApplication>
@ -12,6 +13,7 @@ void registerProxyRoleTypes() {
qmlRegisterType<JoinRole>("SortFilterProxyModel", 0, 2, "JoinRole");
qmlRegisterType<SwitchRole>("SortFilterProxyModel", 0, 2, "SwitchRole");
qmlRegisterType<ExpressionRole>("SortFilterProxyModel", 0, 2, "ExpressionRole");
qmlRegisterType<RegExpRole>("SortFilterProxyModel", 0, 2, "RegExpRole");
}
Q_COREAPP_STARTUP_FUNCTION(registerProxyRoleTypes)

67
proxyroles/regexprole.cpp Normal file
View File

@ -0,0 +1,67 @@
#include "regexprole.h"
#include "qqmlsortfilterproxymodel.h"
#include <QDebug>
namespace qqsfpm {
QString RegExpRole::roleName() const
{
return m_roleName;
}
void RegExpRole::setRoleName(const QString& roleName)
{
if (m_roleName == roleName)
return;
m_roleName = roleName;
Q_EMIT roleNameChanged();
}
QString RegExpRole::pattern() const
{
return m_regularExpression.pattern();
}
void RegExpRole::setPattern(const QString& pattern)
{
if (m_regularExpression.pattern() == pattern)
return;
Q_EMIT namesAboutToBeChanged();
m_regularExpression.setPattern(pattern);
invalidate();
Q_EMIT patternChanged();
Q_EMIT namesChanged();
}
Qt::CaseSensitivity RegExpRole::caseSensitivity() const
{
return m_regularExpression.patternOptions() & QRegularExpression::CaseInsensitiveOption ?
Qt::CaseInsensitive : Qt::CaseSensitive;
}
void RegExpRole::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity)
{
if (this->caseSensitivity() == caseSensitivity)
return;
m_regularExpression.setPatternOptions(m_regularExpression.patternOptions() ^ QRegularExpression::CaseInsensitiveOption); //toggle the option
Q_EMIT caseSensitivityChanged();
}
QStringList RegExpRole::names()
{
QStringList nameCaptureGroups = m_regularExpression.namedCaptureGroups();
nameCaptureGroups.removeAll("");
return nameCaptureGroups;
}
QVariant RegExpRole::data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel, const QString &name)
{
QString text = proxyModel.sourceData(sourceIndex, m_roleName).toString();
QRegularExpressionMatch match = m_regularExpression.match(text);
return match.hasMatch() ? (match.captured(name)) : QVariant{};
}
}

43
proxyroles/regexprole.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef REGEXPROLE_H
#define REGEXPROLE_H
#include "proxyrole.h"
#include <QRegularExpression>
namespace qqsfpm {
class RegExpRole : public ProxyRole
{
Q_OBJECT
Q_PROPERTY(QString roleName READ roleName WRITE setRoleName NOTIFY roleNameChanged)
Q_PROPERTY(QString pattern READ pattern WRITE setPattern NOTIFY patternChanged)
Q_PROPERTY(Qt::CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity NOTIFY caseSensitivityChanged)
public:
using ProxyRole::ProxyRole;
QString roleName() const;
void setRoleName(const QString& roleName);
QString pattern() const;
void setPattern(const QString& pattern);
Qt::CaseSensitivity caseSensitivity() const;
void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity);
QStringList names() override;
Q_SIGNALS:
void roleNameChanged();
void patternChanged();
void caseSensitivityChanged();
private:
QString m_roleName;
QRegularExpression m_regularExpression;
QVariant data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel, const QString &name) override;
};
}
#endif // REGEXPROLE_H

49
proxyroles/singlerole.cpp Normal file
View File

@ -0,0 +1,49 @@
#include "singlerole.h"
#include <QVariant>
namespace qqsfpm {
/*!
\qmltype SingleRole
\inherits ProxyRole
\inqmlmodule SortFilterProxyModel
\brief Base type for the \l SortFilterProxyModel proxy roles defining a single role
SingleRole is a convenience base class for proxy roles who define a single role.
It cannot be used directly in a QML file.
It exists to provide a set of common properties and methods,
available across all the other proxy role types that inherit from it.
Attempting to use the SingleRole type directly will result in an error.
*/
/*!
\qmlproperty string SingleRole::name
This property holds the role name of the proxy role.
*/
QString SingleRole::name() const
{
return m_name;
}
void SingleRole::setName(const QString& name)
{
if (m_name == name)
return;
Q_EMIT namesAboutToBeChanged();
m_name = name;
Q_EMIT nameChanged();
Q_EMIT namesChanged();
}
QStringList SingleRole::names()
{
return QStringList { m_name };
}
QVariant SingleRole::data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel, const QString &name)
{
return data(sourceIndex, proxyModel);
}
}

34
proxyroles/singlerole.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef SINGLEROLE_H
#define SINGLEROLE_H
#include "proxyrole.h"
namespace qqsfpm {
class SingleRole : public ProxyRole
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:
using ProxyRole::ProxyRole;
QString name() const;
void setName(const QString& name);
QStringList names() override;
Q_SIGNALS:
void nameChanged();
private:
QString m_name;
private:
QVariant data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel, const QString &name) final;
virtual QVariant data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel) = 0;
};
}
#endif // SINGLEROLE_H

View File

@ -7,7 +7,7 @@ namespace qqsfpm {
/*!
\qmltype SwitchRole
\inherits ProxyRole
\inherits SingleRole
\inqmlmodule SortFilterProxyModel
\brief a role using \l Filter to conditionnaly compute its data

View File

@ -1,7 +1,7 @@
#ifndef SWITCHROLE_H
#define SWITCHROLE_H
#include "proxyrole.h"
#include "singlerole.h"
#include "filters/filtercontainer.h"
#include <QtQml>
@ -24,7 +24,7 @@ private:
QVariant m_value;
};
class SwitchRole : public ProxyRole, public FilterContainer
class SwitchRole : public SingleRole, public FilterContainer
{
Q_OBJECT
Q_INTERFACES(qqsfpm::FilterContainer)

View File

@ -208,8 +208,9 @@ QVariant QQmlSortFilterProxyModel::sourceData(const QModelIndex& sourceIndex, co
QVariant QQmlSortFilterProxyModel::sourceData(const QModelIndex &sourceIndex, int role) const
{
if (ProxyRole* proxyRole = m_proxyRoleMap[role])
return proxyRole->roleData(sourceIndex, *this);
QPair<ProxyRole*, QString> proxyRolePair = m_proxyRoleMap[role];
if (ProxyRole* proxyRole = proxyRolePair.first)
return proxyRole->roleData(sourceIndex, *this, proxyRolePair.second);
else
return sourceModel()->data(sourceIndex, role);
}
@ -385,10 +386,12 @@ void QQmlSortFilterProxyModel::updateRoleNames()
auto maxIt = std::max_element(roles.cbegin(), roles.cend());
int maxRole = maxIt != roles.cend() ? *maxIt : -1;
for (auto proxyRole : m_proxyRoles) {
++maxRole;
m_roleNames[maxRole] = proxyRole->name().toUtf8();
m_proxyRoleMap[maxRole] = proxyRole;
m_proxyRoleNumbers.append(maxRole);
for (auto roleName : proxyRole->names()) {
++maxRole;
m_roleNames[maxRole] = roleName.toUtf8();
m_proxyRoleMap[maxRole] = {proxyRole, roleName};
m_proxyRoleNumbers.append(maxRole);
}
}
}
@ -484,8 +487,8 @@ void QQmlSortFilterProxyModel::onProxyRoleAppended(ProxyRole *proxyRole)
{
beginResetModel();
connect(proxyRole, &ProxyRole::invalidated, this, &QQmlSortFilterProxyModel::emitProxyRolesChanged);
connect(proxyRole, &ProxyRole::nameAboutToBeChanged, this, &QQmlSortFilterProxyModel::beginResetModel);
connect(proxyRole, &ProxyRole::nameChanged, this, &QQmlSortFilterProxyModel::endResetModel);
connect(proxyRole, &ProxyRole::namesAboutToBeChanged, this, &QQmlSortFilterProxyModel::beginResetModel);
connect(proxyRole, &ProxyRole::namesChanged, this, &QQmlSortFilterProxyModel::endResetModel);
endResetModel();
}

View File

@ -138,7 +138,7 @@ private:
bool m_ascendingSortOrder = true;
bool m_completed = false;
QHash<int, QByteArray> m_roleNames;
QHash<int, ProxyRole*> m_proxyRoleMap;
QHash<int, QPair<ProxyRole*, QString>> m_proxyRoleMap;
QVector<int> m_proxyRoleNumbers;
};

View File

@ -29,4 +29,5 @@ OTHER_FILES += \
tst_expressionrole.qml
DISTFILES += \
tst_filtercontainers.qml
tst_filtercontainers.qml \
tst_regexprole.qml

View File

@ -29,9 +29,20 @@ QVariant SourceIndexRole::data(const QModelIndex& sourceIndex, const qqsfpm::QQm
return sourceIndex.row();
}
QStringList MultiRole::names()
{
return {"role1", "role2"};
}
QVariant MultiRole::data(const QModelIndex&, const qqsfpm::QQmlSortFilterProxyModel&, const QString& name)
{
return "data for " + name;
}
void registerTestRolesTypes() {
qmlRegisterType<StaticRole>("SortFilterProxyModel.Test", 0, 2, "StaticRole");
qmlRegisterType<SourceIndexRole>("SortFilterProxyModel.Test", 0, 2, "SourceIndexRole");
qmlRegisterType<MultiRole>("SortFilterProxyModel.Test", 0, 2, "MultiRole");
}
Q_COREAPP_STARTUP_FUNCTION(registerTestRolesTypes)

View File

@ -1,15 +1,15 @@
#ifndef TESTROLES_H
#define TESTROLES_H
#include "proxyroles/proxyrole.h"
#include "proxyroles/singlerole.h"
#include <QVariant>
class StaticRole : public qqsfpm::ProxyRole
class StaticRole : public qqsfpm::SingleRole
{
Q_OBJECT
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
public:
using qqsfpm::ProxyRole::ProxyRole;
using qqsfpm::SingleRole::SingleRole;
QVariant value() const;
void setValue(const QVariant& value);
@ -24,13 +24,24 @@ private:
QVariant m_value;
};
class SourceIndexRole : public qqsfpm::ProxyRole
class SourceIndexRole : public qqsfpm::SingleRole
{
public:
using qqsfpm::ProxyRole::ProxyRole;
using qqsfpm::SingleRole::SingleRole;
private:
QVariant data(const QModelIndex& sourceIndex, const qqsfpm::QQmlSortFilterProxyModel& proxyModel) override;
};
class MultiRole : public qqsfpm::ProxyRole
{
public:
using qqsfpm::ProxyRole::ProxyRole;
QStringList names() override;
private:
QVariant data(const QModelIndex &sourceIndex, const qqsfpm::QQmlSortFilterProxyModel &proxyModel, const QString &name) override;
};
#endif // TESTROLES_H

View File

@ -41,7 +41,8 @@ Item {
},
SourceIndexRole {
name: "sourceIndexRole"
}
},
MultiRole {}
]
}
@ -79,8 +80,13 @@ Item {
function test_filterFromProxyRole() {
staticRole.value = "filterMe";
compare(testModel.count, 0)
compare(testModel.count, 0);
staticRole.value = "foo";
}
function test_multiRole() {
compare(testModel.get(0, "role1"), "data for role1");
compare(testModel.get(0, "role2"), "data for role2");
}
}
}

54
tests/tst_regexprole.qml Normal file
View File

@ -0,0 +1,54 @@
import QtQuick 2.0
import QtQml 2.2
import QtTest 1.1
import SortFilterProxyModel 0.2
import QtQml 2.2
Item {
ListModel {
id: listModel
ListElement { dummRole: false; compoundRole: "0 - zero"; unusedRole: "" }
ListElement { dummRole: false; compoundRole: "1 - one"; unusedRole: "" }
ListElement { dummRole: false; compoundRole: "2 - two"; unusedRole: "" }
ListElement { dummRole: false; compoundRole: "3 - three"; unusedRole: "" }
ListElement { dummRole: false; compoundRole: "four"; unusedRole: "" }
}
SortFilterProxyModel {
id: testModel
sourceModel: listModel
proxyRoles: [
RegExpRole {
id: regExpRole
roleName: "compoundRole"
pattern: "(?<id>\\d+) - (?<name>.+)"
},
RegExpRole {
id: caseSensitiveRole
roleName: "compoundRole"
pattern: "\\d+ - (?<nameCS>[A-Z]+)"
caseSensitivity: Qt.CaseSensitive
},
RegExpRole {
id: caseInsensitiveRole
roleName: "compoundRole"
pattern: "\\d+ - (?<nameCIS>[A-Z]+)"
caseSensitivity: Qt.CaseInsensitive
}
]
}
TestCase {
name: "RegExpRole"
function test_regExpRole() {
compare(testModel.get(0, "id"), "0");
compare(testModel.get(1, "id"), "1");
compare(testModel.get(0, "name"), "zero");
compare(testModel.get(4, "id"), undefined);
compare(testModel.get(0, "nameCS"), undefined);
compare(testModel.get(0, "nameCIS"), "zero");
}
}
}