refactor: move proxyroles in separate files
This commit is contained in:
parent
2ee1936545
commit
88b5ba4260
|
@ -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
|
||||
|
|
494
proxyrole.cpp
494
proxyrole.cpp
|
@ -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)
|
||||
|
||||
}
|
184
proxyrole.h
184
proxyrole.h
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -3,7 +3,7 @@
|
|||
#include <algorithm>
|
||||
#include "filters/filter.h"
|
||||
#include "sorters/sorter.h"
|
||||
#include "proxyrole.h"
|
||||
#include "proxyroles/proxyrole.h"
|
||||
|
||||
namespace qqsfpm {
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
#ifndef TESTROLES_H
|
||||
#define TESTROLES_H
|
||||
|
||||
#include "proxyrole.h"
|
||||
#include "proxyroles/proxyrole.h"
|
||||
#include <QVariant>
|
||||
|
||||
class StaticRole : public qqsfpm::ProxyRole
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue