refactor: move proxyroles in separate files

This commit is contained in:
Grecko 2018-04-30 02:58:06 +02:00
parent 2ee1936545
commit 88b5ba4260
16 changed files with 779 additions and 684 deletions

View File

@ -3,7 +3,6 @@
INCLUDEPATH += $$PWD
HEADERS += $$PWD/qqmlsortfilterproxymodel.h \
$$PWD/proxyrole.h \
$$PWD/filters/filter.h \
$$PWD/filters/filtercontainer.h \
$$PWD/filters/rolefilter.h \
@ -19,10 +18,14 @@ HEADERS += $$PWD/qqmlsortfilterproxymodel.h \
$$PWD/sorters/sortercontainer.h \
$$PWD/sorters/rolesorter.h \
$$PWD/sorters/stringsorter.h \
$$PWD/sorters/expressionsorter.h
$$PWD/sorters/expressionsorter.h \
$$PWD/proxyroles/proxyrole.h \
$$PWD/proxyroles/proxyrolecontainer.h \
$$PWD/proxyroles/joinrole.h \
$$PWD/proxyroles/switchrole.h \
$$PWD/proxyroles/expressionrole.h
SOURCES += $$PWD/qqmlsortfilterproxymodel.cpp \
$$PWD/proxyrole.cpp \
$$PWD/filters/filter.cpp \
$$PWD/filters/filtercontainer.cpp \
$$PWD/filters/rolefilter.cpp \
@ -40,4 +43,10 @@ SOURCES += $$PWD/qqmlsortfilterproxymodel.cpp \
$$PWD/sorters/rolesorter.cpp \
$$PWD/sorters/stringsorter.cpp \
$$PWD/sorters/expressionsorter.cpp \
$$PWD/sorters/sortersqmltypes.cpp
$$PWD/sorters/sortersqmltypes.cpp \
$$PWD/proxyroles/proxyrole.cpp \
$$PWD/proxyroles/proxyrolecontainer.cpp \
$$PWD/proxyroles/joinrole.cpp \
$$PWD/proxyroles/switchrole.cpp \
$$PWD/proxyroles/expressionrole.cpp \
$$PWD/proxyroles/proxyrolesqmltypes.cpp

View File

@ -1,494 +0,0 @@
#include "proxyrole.h"
#include <QQmlEngine>
#include <QQmlContext>
#include <QQmlExpression>
#include <QCoreApplication>
#include <QDebug>
#include <QQmlInfo>
#include "filters/filter.h"
#include "qqmlsortfilterproxymodel.h"
namespace qqsfpm {
ProxyRoleContainer::~ProxyRoleContainer()
{
}
QList<ProxyRole*> ProxyRoleContainer::proxyRoles() const
{
return m_proxyRoles;
}
void ProxyRoleContainer::appendProxyRole(ProxyRole* proxyRole)
{
m_proxyRoles.append(proxyRole);
onProxyRoleAppended(proxyRole);
}
void ProxyRoleContainer::removeProxyRole(ProxyRole* proxyRole)
{
m_proxyRoles.removeOne(proxyRole);
onProxyRoleRemoved(proxyRole);
}
void ProxyRoleContainer::clearProxyRoles()
{
m_proxyRoles.clear();
onProxyRolesCleared();
}
QQmlListProperty<ProxyRole> ProxyRoleContainer::proxyRolesListProperty()
{
return QQmlListProperty<ProxyRole>(reinterpret_cast<QObject*>(this), &m_proxyRoles,
&ProxyRoleContainer::append_proxyRole,
&ProxyRoleContainer::count_proxyRole,
&ProxyRoleContainer::at_proxyRole,
&ProxyRoleContainer::clear_proxyRoles);
}
void ProxyRoleContainer::append_proxyRole(QQmlListProperty<ProxyRole>* list, ProxyRole* proxyRole)
{
if (!proxyRole)
return;
ProxyRoleContainer* that = reinterpret_cast<ProxyRoleContainer*>(list->object);
that->appendProxyRole(proxyRole);
}
int ProxyRoleContainer::count_proxyRole(QQmlListProperty<ProxyRole>* list)
{
QList<ProxyRole*>* ProxyRoles = static_cast<QList<ProxyRole*>*>(list->data);
return ProxyRoles->count();
}
ProxyRole* ProxyRoleContainer::at_proxyRole(QQmlListProperty<ProxyRole>* list, int index)
{
QList<ProxyRole*>* ProxyRoles = static_cast<QList<ProxyRole*>*>(list->data);
return ProxyRoles->at(index);
}
void ProxyRoleContainer::clear_proxyRoles(QQmlListProperty<ProxyRole> *list)
{
ProxyRoleContainer* that = reinterpret_cast<ProxyRoleContainer*>(list->object);
that->clearProxyRoles();
}
/*!
\qmltype ProxyRole
\inqmlmodule SortFilterProxyModel
\brief Base type for the \l SortFilterProxyModel proxy roles
The ProxyRole type 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 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)
{
if (m_mutex.tryLock()) {
QVariant result = data(sourceIndex, proxyModel);
m_mutex.unlock();
return result;
} else {
return QVariant{};
}
}
void ProxyRole::proxyModelCompleted(const QQmlSortFilterProxyModel &proxyModel)
{
Q_UNUSED(proxyModel)
}
void ProxyRole::invalidate()
{
Q_EMIT invalidated();
}
/*!
\qmltype JoinRole
\inherits ProxyRole
\inqmlmodule SortFilterProxyModel
\brief a role made from concatenating other roles
A JoinRole is a simple \l ProxyRole that concatenates other roles.
In the following example, the \c fullName role is computed by the concatenation of the \c firstName role and the \c lastName role separated by a space :
\code
SortFilterProxyModel {
sourceModel: contactModel
proxyRoles: JoinRole {
name: "fullName"
roleNames: ["firstName", "lastName"]
}
}
\endcode
*/
/*!
\qmlproperty list<string> JoinRole::roleNames
This property holds the role names that are joined by this role.
*/
QStringList JoinRole::roleNames() const
{
return m_roleNames;
}
void JoinRole::setRoleNames(const QStringList& roleNames)
{
if (m_roleNames == roleNames)
return;
m_roleNames = roleNames;
Q_EMIT roleNamesChanged();
invalidate();
}
/*!
\qmlproperty string JoinRole::separator
This property holds the separator that is used to join the roles specified in \l roleNames.
By default, it's a space.
*/
QString JoinRole::separator() const
{
return m_separator;
}
void JoinRole::setSeparator(const QString& separator)
{
if (m_separator == separator)
return;
m_separator = separator;
Q_EMIT separatorChanged();
invalidate();
}
QVariant JoinRole::data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel)
{
QString result;
for (const QString& roleName : m_roleNames)
result += proxyModel.sourceData(sourceIndex, roleName).toString() + m_separator;
if (!m_roleNames.isEmpty())
result.chop(m_separator.length());
return result;
}
/*!
\qmltype SwitchRole
\inherits ProxyRole
\inqmlmodule SortFilterProxyModel
\brief a role using \l Filter to conditionnaly compute its data
A SwitchRole is a \l ProxyRole that computes its data with the help of \l Filter.
Each top level filters specified in the \l SwitchRole is evaluated on the rows of the model, if a \l Filter evaluates to true, the data of the \l SwitchRole for this row will be the one of the attached \l {value} {SwitchRole.value} property.
If no top level filters evaluate to true, the data will default to the one of the \l defaultRoleName (or the \l defaultValue if no \l defaultRoleName is specified).
In the following example, the \c favoriteOrFirstNameSection role is equal to \c * if the \c favorite role of a row is true, otherwise it's the same as the \c firstName role :
\code
SortFilterProxyModel {
sourceModel: contactModel
proxyRoles: SwitchRole {
name: "favoriteOrFirstNameSection"
filters: ValueFilter {
roleName: "favorite"
value: true
SwitchRole.value: "*"
}
defaultRoleName: "firstName"
}
}
\endcode
*/
SwitchRoleAttached::SwitchRoleAttached(QObject* parent) : QObject (parent)
{
if (!qobject_cast<Filter*>(parent))
qmlInfo(parent) << "SwitchRole must be attached to a Filter";
}
/*!
\qmlattachedproperty var SwitchRole::value
This property attaches a value to a \l Filter.
*/
QVariant SwitchRoleAttached::value() const
{
return m_value;
}
void SwitchRoleAttached::setValue(QVariant value)
{
if (m_value == value)
return;
m_value = value;
Q_EMIT valueChanged();
}
/*!
\qmlproperty string SwitchRole::defaultRoleName
This property holds the default role name of the role.
If no filter match a row, the data of this role will be the data of the role whose name is \c defaultRoleName.
*/
QString SwitchRole::defaultRoleName() const
{
return m_defaultRoleName;
}
void SwitchRole::setDefaultRoleName(const QString& defaultRoleName)
{
if (m_defaultRoleName == defaultRoleName)
return;
m_defaultRoleName = defaultRoleName;
Q_EMIT defaultRoleNameChanged();
invalidate();
}
/*!
\qmlproperty var SwitchRole::defaultValue
This property holds the default value of the role.
If no filter match a row, and no \l defaultRoleName is set, the data of this role will be \c defaultValue.
*/
QVariant SwitchRole::defaultValue() const
{
return m_defaultValue;
}
void SwitchRole::setDefaultValue(const QVariant& defaultValue)
{
if (m_defaultValue == defaultValue)
return;
m_defaultValue = defaultValue;
Q_EMIT defaultValueChanged();
invalidate();
}
/*!
\qmlproperty list<Filter> SwitchRole::filters
This property holds the list of filters for this proxy role.
The data of this role will be equal to the attached \l {value} {SwitchRole.value} property of the first filter that matches the model row.
\sa Filter
*/
void SwitchRole::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
for (Filter* filter : m_filters)
filter->proxyModelCompleted(proxyModel);
}
SwitchRoleAttached* SwitchRole::qmlAttachedProperties(QObject* object)
{
return new SwitchRoleAttached(object);
}
QVariant SwitchRole::data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel)
{
for (auto filter: m_filters) {
if (!filter->enabled())
continue;
if (filter->filterAcceptsRow(sourceIndex, proxyModel)) {
auto attached = static_cast<SwitchRoleAttached*>(qmlAttachedPropertiesObject<SwitchRole>(filter, false));
if (!attached) {
qWarning() << "No SwitchRole.value provided for this filter" << filter;
continue;
}
QVariant value = attached->value();
if (!value.isValid()) {
qWarning() << "No SwitchRole.value provided for this filter" << filter;
continue;
}
return value;
}
}
if (!m_defaultRoleName.isEmpty())
return proxyModel.sourceData(sourceIndex, m_defaultRoleName);
return m_defaultValue;
}
void SwitchRole::onFilterAppended(Filter *filter)
{
connect(filter, &Filter::invalidated, this, &SwitchRole::invalidate);
auto attached = static_cast<SwitchRoleAttached*>(qmlAttachedPropertiesObject<SwitchRole>(filter, true));
connect(attached, &SwitchRoleAttached::valueChanged, this, &SwitchRole::invalidate);
invalidate();
}
void SwitchRole::onFilterRemoved(Filter *filter)
{
Q_UNUSED(filter)
invalidate();
}
void SwitchRole::onFiltersCleared()
{
invalidate();
}
/*!
\qmltype ExpressionRole
\inherits ProxyRole
\inqmlmodule SortFilterProxyModel
\brief A custom role computed from a javascrip expression
An ExpressionRole is a \l ProxyRole allowing to implement a custom role based on a javascript expression.
In the following example, the \c c role is computed by adding the \c a role and \c b role of the model :
\code
SortFilterProxyModel {
sourceModel: numberModel
proxyRoles: ExpressionRole {
name: "c"
expression: model.a + model.b
}
}
\endcode
*/
/*!
\qmlproperty expression ExpressionRole::expression
An expression to implement a custom role.
It has the same syntax has a \l {http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html} {Property Binding} except it will be evaluated for each of the source model's rows.
The data for this role will be the retuned valued of the expression.
Data for each row is exposed like for a delegate of a QML View.
This expression is reevaluated for a row every time its model data changes.
When an external property (not \c index or in \c model) the expression depends on changes, the expression is reevaluated for every row of the source model.
To capture the properties the expression depends on, the expression is first executed with invalid data and each property access is detected by the QML engine.
This means that if a property is not accessed because of a conditional, it won't be captured and the expression won't be reevaluted when this property changes.
A workaround to this problem is to access all the properties the expressions depends unconditionally at the beggining of the expression.
*/
const QQmlScriptString& ExpressionRole::expression() const
{
return m_scriptString;
}
void ExpressionRole::setExpression(const QQmlScriptString& scriptString)
{
if (m_scriptString == scriptString)
return;
m_scriptString = scriptString;
updateExpression();
Q_EMIT expressionChanged();
invalidate();
}
void ExpressionRole::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
updateContext(proxyModel);
}
QVariant ExpressionRole::data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel)
{
if (!m_scriptString.isEmpty()) {
QVariantMap modelMap;
QHash<int, QByteArray> roles = proxyModel.roleNames();
QQmlContext context(qmlContext(this));
auto addToContext = [&] (const QString &name, const QVariant& value) {
context.setContextProperty(name, value);
modelMap.insert(name, value);
};
for (auto it = roles.cbegin(); it != roles.cend(); ++it)
addToContext(it.value(), proxyModel.sourceData(sourceIndex, it.key()));
addToContext("index", sourceIndex.row());
context.setContextProperty("model", modelMap);
QQmlExpression expression(m_scriptString, &context);
QVariant result = expression.evaluate();
if (expression.hasError()) {
qWarning() << expression.error();
return true;
}
return result;
}
return QVariant();
}
void ExpressionRole::updateContext(const QQmlSortFilterProxyModel& proxyModel)
{
delete m_context;
m_context = new QQmlContext(qmlContext(this), this);
// what about roles changes ?
QVariantMap modelMap;
auto addToContext = [&] (const QString &name, const QVariant& value) {
m_context->setContextProperty(name, value);
modelMap.insert(name, value);
};
for (const QByteArray& roleName : proxyModel.roleNames().values())
addToContext(roleName, QVariant());
addToContext("index", -1);
m_context->setContextProperty("model", modelMap);
updateExpression();
}
void ExpressionRole::updateExpression()
{
if (!m_context)
return;
delete m_expression;
m_expression = new QQmlExpression(m_scriptString, m_context, 0, this);
connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionRole::invalidate);
m_expression->setNotifyOnValueChanged(true);
m_expression->evaluate();
}
void registerProxyRoleTypes() {
qmlRegisterUncreatableType<ProxyRole>("SortFilterProxyModel", 0, 2, "ProxyRole", "ProxyRole is an abstract class");
qmlRegisterType<JoinRole>("SortFilterProxyModel", 0, 2, "JoinRole");
qmlRegisterType<SwitchRole>("SortFilterProxyModel", 0, 2, "SwitchRole");
qmlRegisterType<ExpressionRole>("SortFilterProxyModel", 0, 2, "ExpressionRole");
}
Q_COREAPP_STARTUP_FUNCTION(registerProxyRoleTypes)
}

View File

@ -1,184 +0,0 @@
#ifndef PROXYROLE_H
#define PROXYROLE_H
#include <QObject>
#include <QMutex>
#include <QQmlScriptString>
#include <QQmlExpression>
#include <qqml.h>
#include "filters/filtercontainer.h"
namespace qqsfpm {
class ProxyRole;
class QQmlSortFilterProxyModel;
class ProxyRoleContainer {
public:
virtual ~ProxyRoleContainer();
QList<ProxyRole*> proxyRoles() const;
void appendProxyRole(ProxyRole* proxyRole);
void removeProxyRole(ProxyRole* proxyRole);
void clearProxyRoles();
QQmlListProperty<ProxyRole> proxyRolesListProperty();
protected:
QList<ProxyRole*> m_proxyRoles;
private:
virtual void onProxyRoleAppended(ProxyRole* proxyRole) = 0;
virtual void onProxyRoleRemoved(ProxyRole* proxyRole) = 0;
virtual void onProxyRolesCleared() = 0;
static void append_proxyRole(QQmlListProperty<ProxyRole>* list, ProxyRole* proxyRole);
static int count_proxyRole(QQmlListProperty<ProxyRole>* list);
static ProxyRole* at_proxyRole(QQmlListProperty<ProxyRole>* list, int index);
static void clear_proxyRoles(QQmlListProperty<ProxyRole>* list);
};
}
#define ProxyRoleContainer_iid "fr.grecko.SortFilterProxyModel.ProxyRoleContainer"
Q_DECLARE_INTERFACE(qqsfpm::ProxyRoleContainer, ProxyRoleContainer_iid)
namespace qqsfpm {
class ProxyRole : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:
explicit ProxyRole(QObject *parent = nullptr);
const QString& name() const;
void setName(const QString& name);
QVariant roleData(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel);
virtual void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel);
protected:
void invalidate();
Q_SIGNALS:
void nameAboutToBeChanged();
void nameChanged();
void invalidated();
private:
virtual QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) = 0;
QString m_name;
QMutex m_mutex;
};
class JoinRole : public ProxyRole
{
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;
QStringList roleNames() const;
void setRoleNames(const QStringList& roleNames);
QString separator() const;
void setSeparator(const QString& separator);
Q_SIGNALS:
void roleNamesChanged();
void separatorChanged();
private:
QStringList m_roleNames;
QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override;
QString m_separator = " ";
};
class SwitchRoleAttached : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
public:
SwitchRoleAttached(QObject* parent);
QVariant value() const;
void setValue(QVariant value);
Q_SIGNALS:
void valueChanged();
private:
QVariant m_value;
};
class SwitchRole : public ProxyRole, public FilterContainer
{
Q_OBJECT
Q_INTERFACES(qqsfpm::FilterContainer)
Q_PROPERTY(QString defaultRoleName READ defaultRoleName WRITE setDefaultRoleName NOTIFY defaultRoleNameChanged)
Q_PROPERTY(QVariant defaultValue READ defaultValue WRITE setDefaultValue NOTIFY defaultValueChanged)
Q_PROPERTY(QQmlListProperty<qqsfpm::Filter> filters READ filtersListProperty)
public:
using ProxyRole::ProxyRole;
QString defaultRoleName() const;
void setDefaultRoleName(const QString& defaultRoleName);
QVariant defaultValue() const;
void setDefaultValue(const QVariant& defaultValue);
void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override;
static SwitchRoleAttached* qmlAttachedProperties(QObject* object);
Q_SIGNALS:
void defaultRoleNameChanged();
void defaultValueChanged();
private:
QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override;
void onFilterAppended(Filter *filter) override;
void onFilterRemoved(Filter *filter) override;
void onFiltersCleared() override;
QString m_defaultRoleName;
QVariant m_defaultValue;
};
class ExpressionRole : public ProxyRole
{
Q_OBJECT
Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged)
public:
using ProxyRole::ProxyRole;
const QQmlScriptString& expression() const;
void setExpression(const QQmlScriptString& scriptString);
void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override;
Q_SIGNALS:
void expressionChanged();
private:
QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override;
void updateContext(const QQmlSortFilterProxyModel& proxyModel);
void updateExpression();
QQmlScriptString m_scriptString;
QQmlExpression* m_expression = nullptr;
QQmlContext* m_context = nullptr;
};
}
QML_DECLARE_TYPEINFO(qqsfpm::SwitchRole, QML_HAS_ATTACHED_PROPERTIES)
#endif // PROXYROLE_H

View File

@ -0,0 +1,127 @@
#include "expressionrole.h"
#include "qqmlsortfilterproxymodel.h"
#include <QtQml>
namespace qqsfpm {
/*!
\qmltype ExpressionRole
\inherits ProxyRole
\inqmlmodule SortFilterProxyModel
\brief A custom role computed from a javascrip expression
An ExpressionRole is a \l ProxyRole allowing to implement a custom role based on a javascript expression.
In the following example, the \c c role is computed by adding the \c a role and \c b role of the model :
\code
SortFilterProxyModel {
sourceModel: numberModel
proxyRoles: ExpressionRole {
name: "c"
expression: model.a + model.b
}
}
\endcode
*/
/*!
\qmlproperty expression ExpressionRole::expression
An expression to implement a custom role.
It has the same syntax has a \l {http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html} {Property Binding} except it will be evaluated for each of the source model's rows.
The data for this role will be the retuned valued of the expression.
Data for each row is exposed like for a delegate of a QML View.
This expression is reevaluated for a row every time its model data changes.
When an external property (not \c index or in \c model) the expression depends on changes, the expression is reevaluated for every row of the source model.
To capture the properties the expression depends on, the expression is first executed with invalid data and each property access is detected by the QML engine.
This means that if a property is not accessed because of a conditional, it won't be captured and the expression won't be reevaluted when this property changes.
A workaround to this problem is to access all the properties the expressions depends unconditionally at the beggining of the expression.
*/
const QQmlScriptString& ExpressionRole::expression() const
{
return m_scriptString;
}
void ExpressionRole::setExpression(const QQmlScriptString& scriptString)
{
if (m_scriptString == scriptString)
return;
m_scriptString = scriptString;
updateExpression();
Q_EMIT expressionChanged();
invalidate();
}
void ExpressionRole::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
updateContext(proxyModel);
}
QVariant ExpressionRole::data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel)
{
if (!m_scriptString.isEmpty()) {
QVariantMap modelMap;
QHash<int, QByteArray> roles = proxyModel.roleNames();
QQmlContext context(qmlContext(this));
auto addToContext = [&] (const QString &name, const QVariant& value) {
context.setContextProperty(name, value);
modelMap.insert(name, value);
};
for (auto it = roles.cbegin(); it != roles.cend(); ++it)
addToContext(it.value(), proxyModel.sourceData(sourceIndex, it.key()));
addToContext("index", sourceIndex.row());
context.setContextProperty("model", modelMap);
QQmlExpression expression(m_scriptString, &context);
QVariant result = expression.evaluate();
if (expression.hasError()) {
qWarning() << expression.error();
return true;
}
return result;
}
return QVariant();
}
void ExpressionRole::updateContext(const QQmlSortFilterProxyModel& proxyModel)
{
delete m_context;
m_context = new QQmlContext(qmlContext(this), this);
// what about roles changes ?
QVariantMap modelMap;
auto addToContext = [&] (const QString &name, const QVariant& value) {
m_context->setContextProperty(name, value);
modelMap.insert(name, value);
};
for (const QByteArray& roleName : proxyModel.roleNames().values())
addToContext(roleName, QVariant());
addToContext("index", -1);
m_context->setContextProperty("model", modelMap);
updateExpression();
}
void ExpressionRole::updateExpression()
{
if (!m_context)
return;
delete m_expression;
m_expression = new QQmlExpression(m_scriptString, m_context, 0, this);
connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionRole::invalidate);
m_expression->setNotifyOnValueChanged(true);
m_expression->evaluate();
}
}

View File

@ -0,0 +1,39 @@
#ifndef EXPRESSIONROLE_H
#define EXPRESSIONROLE_H
#include "proxyrole.h"
#include <QQmlScriptString>
class QQmlExpression;
namespace qqsfpm {
class ExpressionRole : public ProxyRole
{
Q_OBJECT
Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged)
public:
using ProxyRole::ProxyRole;
const QQmlScriptString& expression() const;
void setExpression(const QQmlScriptString& scriptString);
void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override;
Q_SIGNALS:
void expressionChanged();
private:
QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override;
void updateContext(const QQmlSortFilterProxyModel& proxyModel);
void updateExpression();
QQmlScriptString m_scriptString;
QQmlExpression* m_expression = nullptr;
QQmlContext* m_context = nullptr;
};
}
#endif // EXPRESSIONROLE_H

82
proxyroles/joinrole.cpp Normal file
View File

@ -0,0 +1,82 @@
#include "joinrole.h"
#include "qqmlsortfilterproxymodel.h"
namespace qqsfpm {
/*!
\qmltype JoinRole
\inherits ProxyRole
\inqmlmodule SortFilterProxyModel
\brief a role made from concatenating other roles
A JoinRole is a simple \l ProxyRole that concatenates other roles.
In the following example, the \c fullName role is computed by the concatenation of the \c firstName role and the \c lastName role separated by a space :
\code
SortFilterProxyModel {
sourceModel: contactModel
proxyRoles: JoinRole {
name: "fullName"
roleNames: ["firstName", "lastName"]
}
}
\endcode
*/
/*!
\qmlproperty list<string> JoinRole::roleNames
This property holds the role names that are joined by this role.
*/
QStringList JoinRole::roleNames() const
{
return m_roleNames;
}
void JoinRole::setRoleNames(const QStringList& roleNames)
{
if (m_roleNames == roleNames)
return;
m_roleNames = roleNames;
Q_EMIT roleNamesChanged();
invalidate();
}
/*!
\qmlproperty string JoinRole::separator
This property holds the separator that is used to join the roles specified in \l roleNames.
By default, it's a space.
*/
QString JoinRole::separator() const
{
return m_separator;
}
void JoinRole::setSeparator(const QString& separator)
{
if (m_separator == separator)
return;
m_separator = separator;
Q_EMIT separatorChanged();
invalidate();
}
QVariant JoinRole::data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel)
{
QString result;
for (const QString& roleName : m_roleNames)
result += proxyModel.sourceData(sourceIndex, roleName).toString() + m_separator;
if (!m_roleNames.isEmpty())
result.chop(m_separator.length());
return result;
}
}

36
proxyroles/joinrole.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef JOINROLE_H
#define JOINROLE_H
#include "proxyrole.h"
namespace qqsfpm {
class JoinRole : public ProxyRole
{
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;
QStringList roleNames() const;
void setRoleNames(const QStringList& roleNames);
QString separator() const;
void setSeparator(const QString& separator);
Q_SIGNALS:
void roleNamesChanged();
void separatorChanged();
private:
QStringList m_roleNames;
QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override;
QString m_separator = " ";
};
}
#endif // JOINROLE_H

75
proxyroles/proxyrole.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "proxyrole.h"
#include <QQmlEngine>
#include <QQmlContext>
#include <QQmlExpression>
#include <QCoreApplication>
#include <QDebug>
#include <QQmlInfo>
#include "filters/filter.h"
#include "qqmlsortfilterproxymodel.h"
namespace qqsfpm {
/*!
\qmltype ProxyRole
\inqmlmodule SortFilterProxyModel
\brief Base type for the \l SortFilterProxyModel proxy roles
The ProxyRole type 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 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)
{
if (m_mutex.tryLock()) {
QVariant result = data(sourceIndex, proxyModel);
m_mutex.unlock();
return result;
} else {
return QVariant{};
}
}
void ProxyRole::proxyModelCompleted(const QQmlSortFilterProxyModel &proxyModel)
{
Q_UNUSED(proxyModel)
}
void ProxyRole::invalidate()
{
Q_EMIT invalidated();
}
}

42
proxyroles/proxyrole.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef PROXYROLE_H
#define PROXYROLE_H
#include <QObject>
#include <QMutex>
namespace qqsfpm {
class QQmlSortFilterProxyModel;
class ProxyRole : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:
explicit ProxyRole(QObject *parent = nullptr);
const QString& name() const;
void setName(const QString& name);
QVariant roleData(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel);
virtual void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel);
protected:
void invalidate();
Q_SIGNALS:
void nameAboutToBeChanged();
void nameChanged();
void invalidated();
private:
virtual QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) = 0;
QString m_name;
QMutex m_mutex;
};
}
#endif // PROXYROLE_H

View File

@ -0,0 +1,69 @@
#include "proxyrolecontainer.h"
namespace qqsfpm {
ProxyRoleContainer::~ProxyRoleContainer()
{
}
QList<ProxyRole*> ProxyRoleContainer::proxyRoles() const
{
return m_proxyRoles;
}
void ProxyRoleContainer::appendProxyRole(ProxyRole* proxyRole)
{
m_proxyRoles.append(proxyRole);
onProxyRoleAppended(proxyRole);
}
void ProxyRoleContainer::removeProxyRole(ProxyRole* proxyRole)
{
m_proxyRoles.removeOne(proxyRole);
onProxyRoleRemoved(proxyRole);
}
void ProxyRoleContainer::clearProxyRoles()
{
m_proxyRoles.clear();
onProxyRolesCleared();
}
QQmlListProperty<ProxyRole> ProxyRoleContainer::proxyRolesListProperty()
{
return QQmlListProperty<ProxyRole>(reinterpret_cast<QObject*>(this), &m_proxyRoles,
&ProxyRoleContainer::append_proxyRole,
&ProxyRoleContainer::count_proxyRole,
&ProxyRoleContainer::at_proxyRole,
&ProxyRoleContainer::clear_proxyRoles);
}
void ProxyRoleContainer::append_proxyRole(QQmlListProperty<ProxyRole>* list, ProxyRole* proxyRole)
{
if (!proxyRole)
return;
ProxyRoleContainer* that = reinterpret_cast<ProxyRoleContainer*>(list->object);
that->appendProxyRole(proxyRole);
}
int ProxyRoleContainer::count_proxyRole(QQmlListProperty<ProxyRole>* list)
{
QList<ProxyRole*>* ProxyRoles = static_cast<QList<ProxyRole*>*>(list->data);
return ProxyRoles->count();
}
ProxyRole* ProxyRoleContainer::at_proxyRole(QQmlListProperty<ProxyRole>* list, int index)
{
QList<ProxyRole*>* ProxyRoles = static_cast<QList<ProxyRole*>*>(list->data);
return ProxyRoles->at(index);
}
void ProxyRoleContainer::clear_proxyRoles(QQmlListProperty<ProxyRole> *list)
{
ProxyRoleContainer* that = reinterpret_cast<ProxyRoleContainer*>(list->object);
that->clearProxyRoles();
}
}

View File

@ -0,0 +1,42 @@
#ifndef PROXYROLECONTAINER_H
#define PROXYROLECONTAINER_H
#include <QList>
#include <QQmlListProperty>
namespace qqsfpm {
class ProxyRole;
class QQmlSortFilterProxyModel;
class ProxyRoleContainer {
public:
virtual ~ProxyRoleContainer();
QList<ProxyRole*> proxyRoles() const;
void appendProxyRole(ProxyRole* proxyRole);
void removeProxyRole(ProxyRole* proxyRole);
void clearProxyRoles();
QQmlListProperty<ProxyRole> proxyRolesListProperty();
protected:
QList<ProxyRole*> m_proxyRoles;
private:
virtual void onProxyRoleAppended(ProxyRole* proxyRole) = 0;
virtual void onProxyRoleRemoved(ProxyRole* proxyRole) = 0;
virtual void onProxyRolesCleared() = 0;
static void append_proxyRole(QQmlListProperty<ProxyRole>* list, ProxyRole* proxyRole);
static int count_proxyRole(QQmlListProperty<ProxyRole>* list);
static ProxyRole* at_proxyRole(QQmlListProperty<ProxyRole>* list, int index);
static void clear_proxyRoles(QQmlListProperty<ProxyRole>* list);
};
}
#define ProxyRoleContainer_iid "fr.grecko.SortFilterProxyModel.ProxyRoleContainer"
Q_DECLARE_INTERFACE(qqsfpm::ProxyRoleContainer, ProxyRoleContainer_iid)
#endif // PROXYROLECONTAINER_H

View File

@ -0,0 +1,19 @@
#include "proxyrole.h"
#include "joinrole.h"
#include "switchrole.h"
#include "expressionrole.h"
#include <QQmlEngine>
#include <QCoreApplication>
namespace qqsfpm {
void registerProxyRoleTypes() {
qmlRegisterUncreatableType<ProxyRole>("SortFilterProxyModel", 0, 2, "ProxyRole", "ProxyRole is an abstract class");
qmlRegisterType<JoinRole>("SortFilterProxyModel", 0, 2, "JoinRole");
qmlRegisterType<SwitchRole>("SortFilterProxyModel", 0, 2, "SwitchRole");
qmlRegisterType<ExpressionRole>("SortFilterProxyModel", 0, 2, "ExpressionRole");
}
Q_COREAPP_STARTUP_FUNCTION(registerProxyRoleTypes)
}

165
proxyroles/switchrole.cpp Normal file
View File

@ -0,0 +1,165 @@
#include "switchrole.h"
#include "qqmlsortfilterproxymodel.h"
#include "filters/filter.h"
#include <QtQml>
namespace qqsfpm {
/*!
\qmltype SwitchRole
\inherits ProxyRole
\inqmlmodule SortFilterProxyModel
\brief a role using \l Filter to conditionnaly compute its data
A SwitchRole is a \l ProxyRole that computes its data with the help of \l Filter.
Each top level filters specified in the \l SwitchRole is evaluated on the rows of the model, if a \l Filter evaluates to true, the data of the \l SwitchRole for this row will be the one of the attached \l {value} {SwitchRole.value} property.
If no top level filters evaluate to true, the data will default to the one of the \l defaultRoleName (or the \l defaultValue if no \l defaultRoleName is specified).
In the following example, the \c favoriteOrFirstNameSection role is equal to \c * if the \c favorite role of a row is true, otherwise it's the same as the \c firstName role :
\code
SortFilterProxyModel {
sourceModel: contactModel
proxyRoles: SwitchRole {
name: "favoriteOrFirstNameSection"
filters: ValueFilter {
roleName: "favorite"
value: true
SwitchRole.value: "*"
}
defaultRoleName: "firstName"
}
}
\endcode
*/
SwitchRoleAttached::SwitchRoleAttached(QObject* parent) : QObject (parent)
{
if (!qobject_cast<Filter*>(parent))
qmlInfo(parent) << "SwitchRole must be attached to a Filter";
}
/*!
\qmlattachedproperty var SwitchRole::value
This property attaches a value to a \l Filter.
*/
QVariant SwitchRoleAttached::value() const
{
return m_value;
}
void SwitchRoleAttached::setValue(QVariant value)
{
if (m_value == value)
return;
m_value = value;
Q_EMIT valueChanged();
}
/*!
\qmlproperty string SwitchRole::defaultRoleName
This property holds the default role name of the role.
If no filter match a row, the data of this role will be the data of the role whose name is \c defaultRoleName.
*/
QString SwitchRole::defaultRoleName() const
{
return m_defaultRoleName;
}
void SwitchRole::setDefaultRoleName(const QString& defaultRoleName)
{
if (m_defaultRoleName == defaultRoleName)
return;
m_defaultRoleName = defaultRoleName;
Q_EMIT defaultRoleNameChanged();
invalidate();
}
/*!
\qmlproperty var SwitchRole::defaultValue
This property holds the default value of the role.
If no filter match a row, and no \l defaultRoleName is set, the data of this role will be \c defaultValue.
*/
QVariant SwitchRole::defaultValue() const
{
return m_defaultValue;
}
void SwitchRole::setDefaultValue(const QVariant& defaultValue)
{
if (m_defaultValue == defaultValue)
return;
m_defaultValue = defaultValue;
Q_EMIT defaultValueChanged();
invalidate();
}
/*!
\qmlproperty list<Filter> SwitchRole::filters
This property holds the list of filters for this proxy role.
The data of this role will be equal to the attached \l {value} {SwitchRole.value} property of the first filter that matches the model row.
\sa Filter
*/
void SwitchRole::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
for (Filter* filter : m_filters)
filter->proxyModelCompleted(proxyModel);
}
SwitchRoleAttached* SwitchRole::qmlAttachedProperties(QObject* object)
{
return new SwitchRoleAttached(object);
}
QVariant SwitchRole::data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel)
{
for (auto filter: m_filters) {
if (!filter->enabled())
continue;
if (filter->filterAcceptsRow(sourceIndex, proxyModel)) {
auto attached = static_cast<SwitchRoleAttached*>(qmlAttachedPropertiesObject<SwitchRole>(filter, false));
if (!attached) {
qWarning() << "No SwitchRole.value provided for this filter" << filter;
continue;
}
QVariant value = attached->value();
if (!value.isValid()) {
qWarning() << "No SwitchRole.value provided for this filter" << filter;
continue;
}
return value;
}
}
if (!m_defaultRoleName.isEmpty())
return proxyModel.sourceData(sourceIndex, m_defaultRoleName);
return m_defaultValue;
}
void SwitchRole::onFilterAppended(Filter *filter)
{
connect(filter, &Filter::invalidated, this, &SwitchRole::invalidate);
auto attached = static_cast<SwitchRoleAttached*>(qmlAttachedPropertiesObject<SwitchRole>(filter, true));
connect(attached, &SwitchRoleAttached::valueChanged, this, &SwitchRole::invalidate);
invalidate();
}
void SwitchRole::onFilterRemoved(Filter *filter)
{
Q_UNUSED(filter)
invalidate();
}
void SwitchRole::onFiltersCleared()
{
invalidate();
}
}

67
proxyroles/switchrole.h Normal file
View File

@ -0,0 +1,67 @@
#ifndef SWITCHROLE_H
#define SWITCHROLE_H
#include "proxyrole.h"
#include "filters/filtercontainer.h"
#include <QtQml>
namespace qqsfpm {
class SwitchRoleAttached : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
public:
SwitchRoleAttached(QObject* parent);
QVariant value() const;
void setValue(QVariant value);
Q_SIGNALS:
void valueChanged();
private:
QVariant m_value;
};
class SwitchRole : public ProxyRole, public FilterContainer
{
Q_OBJECT
Q_INTERFACES(qqsfpm::FilterContainer)
Q_PROPERTY(QString defaultRoleName READ defaultRoleName WRITE setDefaultRoleName NOTIFY defaultRoleNameChanged)
Q_PROPERTY(QVariant defaultValue READ defaultValue WRITE setDefaultValue NOTIFY defaultValueChanged)
Q_PROPERTY(QQmlListProperty<qqsfpm::Filter> filters READ filtersListProperty)
public:
using ProxyRole::ProxyRole;
QString defaultRoleName() const;
void setDefaultRoleName(const QString& defaultRoleName);
QVariant defaultValue() const;
void setDefaultValue(const QVariant& defaultValue);
void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override;
static SwitchRoleAttached* qmlAttachedProperties(QObject* object);
Q_SIGNALS:
void defaultRoleNameChanged();
void defaultValueChanged();
private:
QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override;
void onFilterAppended(Filter *filter) override;
void onFilterRemoved(Filter *filter) override;
void onFiltersCleared() override;
QString m_defaultRoleName;
QVariant m_defaultValue;
};
}
QML_DECLARE_TYPEINFO(qqsfpm::SwitchRole, QML_HAS_ATTACHED_PROPERTIES)
#endif // SWITCHROLE_H

View File

@ -3,7 +3,7 @@
#include <algorithm>
#include "filters/filter.h"
#include "sorters/sorter.h"
#include "proxyrole.h"
#include "proxyroles/proxyrole.h"
namespace qqsfpm {

View File

@ -1,7 +1,8 @@
#ifndef TESTROLES_H
#define TESTROLES_H
#include "proxyrole.h"
#include "proxyroles/proxyrole.h"
#include <QVariant>
class StaticRole : public qqsfpm::ProxyRole
{