diff --git a/proxyrole.cpp b/proxyrole.cpp index 1001c80..a19121d 100644 --- a/proxyrole.cpp +++ b/proxyrole.cpp @@ -218,10 +218,96 @@ void SwitchRole::clear_filters(QQmlListProperty *list) that->invalidate(); } +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 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("SortFilterProxyModel", 0, 2, "ProxyRole", "ProxyRole is an abstract class"); qmlRegisterType("SortFilterProxyModel", 0, 2, "JoinRole"); qmlRegisterType("SortFilterProxyModel", 0, 2, "SwitchRole"); + qmlRegisterType("SortFilterProxyModel", 0, 2, "ExpressionRole"); } Q_COREAPP_STARTUP_FUNCTION(registerProxyRoleTypes) diff --git a/proxyrole.h b/proxyrole.h index 485e5a4..1f132ad 100644 --- a/proxyrole.h +++ b/proxyrole.h @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include "qqmlsortfilterproxymodel.h" @@ -118,6 +120,32 @@ private: QList m_filters; }; +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) diff --git a/qqmlsortfilterproxymodel.cpp b/qqmlsortfilterproxymodel.cpp index 62e6f6d..31e1068 100644 --- a/qqmlsortfilterproxymodel.cpp +++ b/qqmlsortfilterproxymodel.cpp @@ -205,8 +205,14 @@ void QQmlSortFilterProxyModel::classBegin() void QQmlSortFilterProxyModel::componentComplete() { m_completed = true; + for (const auto& filter : m_filters) filter->proxyModelCompleted(*this); + for (const auto& sorter : m_sorters) + sorter->proxyModelCompleted(*this); + for (const auto& proxyRole : m_proxyRoles) + proxyRole->proxyModelCompleted(*this); + invalidate(); sort(0); } diff --git a/tests/SortFilterProxyModel.pro b/tests/SortFilterProxyModel.pro index 62f7179..02e454d 100644 --- a/tests/SortFilterProxyModel.pro +++ b/tests/SortFilterProxyModel.pro @@ -25,4 +25,5 @@ OTHER_FILES += \ tst_stringsorter.qml \ tst_proxyroles.qml \ tst_joinrole.qml \ - tst_switchrole.qml + tst_switchrole.qml \ + tst_expressionrole.qml diff --git a/tests/tst_expressionrole.qml b/tests/tst_expressionrole.qml new file mode 100644 index 0000000..8bc0856 --- /dev/null +++ b/tests/tst_expressionrole.qml @@ -0,0 +1,43 @@ +import QtQuick 2.0 +import QtQml 2.2 +import QtTest 1.1 +import SortFilterProxyModel 0.2 +import QtQml 2.2 + +Item { + property int c: 0 + ListModel { + id: listModel + ListElement { a: 1; b: 2 } + } + + SortFilterProxyModel { + id: testModel + sourceModel: listModel + + proxyRoles: ExpressionRole { + name: "expressionRole" + expression: a + model.b + c + } + } + + Instantiator { + id: instantiator + model: testModel + QtObject { + property string expressionRole: model.expressionRole + } + } + + TestCase { + name: "ExpressionRole" + + function test_expressionRole() { + fuzzyCompare(instantiator.object.expressionRole, 3, 1e-7); + listModel.setProperty(0, "b", 9); + fuzzyCompare(instantiator.object.expressionRole, 10, 1e-7); + c = 1327; + fuzzyCompare(instantiator.object.expressionRole, 1337, 1e-7); + } + } +}