feat: Add delayed property

This commit is contained in:
grecko 2019-07-25 00:18:37 +02:00
parent 770789ee48
commit 36befddf5d
4 changed files with 309 additions and 19 deletions

View File

@ -26,7 +26,12 @@ namespace qqsfpm {
*/
QQmlSortFilterProxyModel::QQmlSortFilterProxyModel(QObject *parent) :
QSortFilterProxyModel(parent)
QSortFilterProxyModel(parent),
#ifdef SFPM_DELAYED
m_delayed(true)
#else
m_delayed(false)
#endif
{
connect(this, &QAbstractProxyModel::sourceModelChanged, this, &QQmlSortFilterProxyModel::updateRoles);
connect(this, &QAbstractItemModel::modelReset, this, &QQmlSortFilterProxyModel::updateRoles);
@ -55,6 +60,30 @@ int QQmlSortFilterProxyModel::count() const
return rowCount();
}
/*!
\qmlproperty int SortFilterProxyModel::delayed
Delay the execution of filters, sorters and proxyRoles until the next event loop.
This can be used as an optimization when multiple filters, sorters or proxyRoles are changed in a single event loop.
They will be executed once in a single batch at the next event loop instead of being executed in multiple sequential batches.
By default, the SortFilterProxyModel is not delayed, unless the SFPM_DELAYED is defined at compile time.
*/
bool QQmlSortFilterProxyModel::delayed() const
{
return m_delayed;
}
void QQmlSortFilterProxyModel::setDelayed(bool delayed)
{
if (m_delayed == delayed)
return;
m_delayed = delayed;
Q_EMIT delayedChanged();
}
const QString& QQmlSortFilterProxyModel::filterRoleName() const
{
return m_filterRoleName;
@ -114,7 +143,7 @@ void QQmlSortFilterProxyModel::setFilterValue(const QVariant& filterValue)
return;
m_filterValue = filterValue;
invalidateFilter();
queueInvalidateFilter();
Q_EMIT filterValueChanged();
}
@ -153,7 +182,7 @@ void QQmlSortFilterProxyModel::setAscendingSortOrder(bool ascendingSortOrder)
m_ascendingSortOrder = ascendingSortOrder;
Q_EMIT ascendingSortOrderChanged();
invalidate();
queueInvalidate();
}
/*!
@ -362,14 +391,40 @@ void QQmlSortFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel)
QSortFilterProxyModel::setSourceModel(sourceModel);
}
void QQmlSortFilterProxyModel::queueInvalidateFilter()
{
if (m_delayed) {
if (!m_invalidateFilterQueued && !m_invalidateQueued) {
m_invalidateFilterQueued = true;
QMetaObject::invokeMethod(this, "invalidateFilter", Qt::QueuedConnection);
}
} else {
invalidateFilter();
}
}
void QQmlSortFilterProxyModel::invalidateFilter()
{
if (m_completed)
m_invalidateFilterQueued = false;
if (m_completed && !m_invalidateQueued)
QSortFilterProxyModel::invalidateFilter();
}
void QQmlSortFilterProxyModel::queueInvalidate()
{
if (m_delayed) {
if (!m_invalidateQueued) {
m_invalidateQueued = true;
QMetaObject::invokeMethod(this, "invalidate", Qt::QueuedConnection);
}
} else {
invalidate();
}
}
void QQmlSortFilterProxyModel::invalidate()
{
m_invalidateQueued = false;
if (m_completed)
QSortFilterProxyModel::invalidate();
}
@ -410,7 +465,7 @@ void QQmlSortFilterProxyModel::updateSortRole()
if (!sortRoles.empty())
{
setSortRole(sortRoles.first());
invalidate();
queueInvalidate();
}
}
@ -434,10 +489,24 @@ void QQmlSortFilterProxyModel::onDataChanged(const QModelIndex& topLeft, const Q
Q_EMIT dataChanged(topLeft, bottomRight, m_proxyRoleNumbers);
}
void QQmlSortFilterProxyModel::emitProxyRolesChanged()
void QQmlSortFilterProxyModel::queueInvalidateProxyRoles()
{
invalidate();
Q_EMIT dataChanged(index(0,0), index(rowCount() - 1, columnCount() - 1), m_proxyRoleNumbers);
queueInvalidate();
if (m_delayed) {
if (!m_invalidateProxyRolesQueued) {
m_invalidateProxyRolesQueued = true;
QMetaObject::invokeMethod(this, "invalidateProxyRoles", Qt::QueuedConnection);
}
} else {
invalidateProxyRoles();
}
}
void QQmlSortFilterProxyModel::invalidateProxyRoles()
{
m_invalidateProxyRolesQueued = false;
if (m_completed)
Q_EMIT dataChanged(index(0,0), index(rowCount() - 1, columnCount() - 1), m_proxyRoleNumbers);
}
QVariantMap QQmlSortFilterProxyModel::modelDataMap(const QModelIndex& modelIndex) const
@ -451,42 +520,42 @@ QVariantMap QQmlSortFilterProxyModel::modelDataMap(const QModelIndex& modelIndex
void QQmlSortFilterProxyModel::onFilterAppended(Filter* filter)
{
connect(filter, &Filter::invalidated, this, &QQmlSortFilterProxyModel::invalidateFilter);
this->invalidateFilter();
connect(filter, &Filter::invalidated, this, &QQmlSortFilterProxyModel::queueInvalidateFilter);
queueInvalidateFilter();
}
void QQmlSortFilterProxyModel::onFilterRemoved(Filter* filter)
{
Q_UNUSED(filter)
invalidateFilter();
queueInvalidateFilter();
}
void QQmlSortFilterProxyModel::onFiltersCleared()
{
invalidateFilter();
queueInvalidateFilter();
}
void QQmlSortFilterProxyModel::onSorterAppended(Sorter* sorter)
{
connect(sorter, &Sorter::invalidated, this, &QQmlSortFilterProxyModel::invalidate);
invalidate();
connect(sorter, &Sorter::invalidated, this, &QQmlSortFilterProxyModel::queueInvalidate);
queueInvalidate();
}
void QQmlSortFilterProxyModel::onSorterRemoved(Sorter* sorter)
{
Q_UNUSED(sorter)
invalidate();
queueInvalidate();
}
void QQmlSortFilterProxyModel::onSortersCleared()
{
invalidate();
queueInvalidate();
}
void QQmlSortFilterProxyModel::onProxyRoleAppended(ProxyRole *proxyRole)
{
beginResetModel();
connect(proxyRole, &ProxyRole::invalidated, this, &QQmlSortFilterProxyModel::emitProxyRolesChanged);
connect(proxyRole, &ProxyRole::invalidated, this, &QQmlSortFilterProxyModel::queueInvalidateProxyRoles);
connect(proxyRole, &ProxyRole::namesAboutToBeChanged, this, &QQmlSortFilterProxyModel::beginResetModel);
connect(proxyRole, &ProxyRole::namesChanged, this, &QQmlSortFilterProxyModel::endResetModel);
endResetModel();

View File

@ -22,6 +22,7 @@ class QQmlSortFilterProxyModel : public QSortFilterProxyModel,
Q_INTERFACES(qqsfpm::ProxyRoleContainer)
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(bool delayed READ delayed WRITE setDelayed NOTIFY delayedChanged)
Q_PROPERTY(QString filterRoleName READ filterRoleName WRITE setFilterRoleName NOTIFY filterRoleNameChanged)
Q_PROPERTY(QString filterPattern READ filterPattern WRITE setFilterPattern NOTIFY filterPatternChanged)
@ -49,6 +50,9 @@ public:
int count() const;
bool delayed() const;
void setDelayed(bool delayed);
const QString& filterRoleName() const;
void setFilterRoleName(const QString& filterRoleName);
@ -90,6 +94,7 @@ public:
Q_SIGNALS:
void countChanged();
void delayedChanged();
void filterRoleNameChanged();
void filterPatternSyntaxChanged();
@ -107,7 +112,9 @@ protected Q_SLOTS:
void resetInternalData();
private Q_SLOTS:
void queueInvalidateFilter();
void invalidateFilter();
void queueInvalidate();
void invalidate();
void updateRoleNames();
void updateFilterRole();
@ -115,7 +122,8 @@ private Q_SLOTS:
void updateRoles();
void initRoles();
void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles);
void emitProxyRolesChanged();
void queueInvalidateProxyRoles();
void invalidateProxyRoles();
private:
QVariantMap modelDataMap(const QModelIndex& modelIndex) const;
@ -132,6 +140,7 @@ private:
void onProxyRoleRemoved(ProxyRole *proxyRole) override;
void onProxyRolesCleared() override;
bool m_delayed;
QString m_filterRoleName;
QVariant m_filterValue;
QString m_sortRoleName;
@ -140,6 +149,10 @@ private:
QHash<int, QByteArray> m_roleNames;
QHash<int, QPair<ProxyRole*, QString>> m_proxyRoleMap;
QVector<int> m_proxyRoleNumbers;
bool m_invalidateFilterQueued = false;
bool m_invalidateQueued = false;
bool m_invalidateProxyRolesQueued = false;
};
}

View File

@ -32,4 +32,5 @@ DISTFILES += \
tst_filtercontainers.qml \
tst_regexprole.qml \
tst_filtersorter.qml \
tst_filterrole.qml
tst_filterrole.qml \
tst_delayed.qml

207
tests/tst_delayed.qml Normal file
View File

@ -0,0 +1,207 @@
import QtQuick 2.0
import QtQml 2.2
import QtTest 1.1
import SortFilterProxyModel 0.2
import SortFilterProxyModel.Test 0.2
Item {
ListModel {
id: testModel1
ListElement{ role1: 1 }
}
SortFilterProxyModel {
id: testFilterProxyModel
sourceModel: testModel1
property int foo: 1
filters: [
ExpressionFilter {
id: expressionFilter
property var w: ({count : 0}) // wrap count in a js object so modifying it doesn't bind it in the expression
expression: {
++w.count;
testFilterProxyModel.foo;
return true;
}
},
ValueFilter {
roleName: "role1"
value: testFilterProxyModel.foo
},
ValueFilter {
roleName: "role1"
value: testFilterProxyModel.foo
}
]
sorters: RoleSorter {
roleName: "role1"
sortOrder: testFilterProxyModel.foo === 1 ? Qt.AscendingOrder : Qt.DescendingOrder
}
}
ListModel {
id: testModel2
ListElement{ role1: 1 }
ListElement{ role1: 2 }
}
SortFilterProxyModel {
id: testSorterProxyModel
sourceModel: testModel2
property bool foo: true
sorters: [
ExpressionSorter {
id: expressionSorter
property var w: ({count : 0}) // wrap count in a js object so modifying it doesn't bind it in the expression
expression: {
++w.count;
testSorterProxyModel.foo;
return false;
}
},
RoleSorter {
roleName: "role1"
sortOrder: testSorterProxyModel.foo ? Qt.AscendingOrder : Qt.DescendingOrder
},
RoleSorter {
roleName: "role1"
sortOrder: testSorterProxyModel.foo ? Qt.AscendingOrder : Qt.DescendingOrder
}
]
}
SortFilterProxyModel {
id: testRolesProxyModel
sourceModel: testModel1
property bool foo: true
proxyRoles: [
StaticRole {
name: "display"
value: 5
},
ExpressionRole {
id: expressionRole
name: "expressionRole"
property var w: ({count : 0}) // wrap count in a js object so modifying it doesn't bind it in the expression
expression: {
++w.count;
return testRolesProxyModel.foo;
}
},
StaticRole {
name: "role1"
value: testRolesProxyModel.foo
},
StaticRole {
name: "role2"
value: testRolesProxyModel.foo
}
]
}
SignalSpy {
id: dataChangedSpy
target: testRolesProxyModel
signalName: "dataChanged"
}
Instantiator {
id: instantiator
model: testRolesProxyModel
delegate: QtObject { property bool foo: model.expressionRole; property bool foo2: model.expressionRole }
}
TestCase {
name: "DelayedTest"
function test_directFilters() {
testFilterProxyModel.delayed = false;
expressionFilter.w.count = 0;
testFilterProxyModel.foo = 2;
compare(testFilterProxyModel.count, 0);
compare(expressionFilter.w.count, 4);
wait(0);
compare(testFilterProxyModel.count, 0);
compare(expressionFilter.w.count, 4);
}
function test_delayedFilters() {
testFilterProxyModel.delayed = false;
testFilterProxyModel.foo = 2;
compare(testFilterProxyModel.count, 0);
testFilterProxyModel.delayed = true;
expressionFilter.w.count = 0;
testFilterProxyModel.foo = 0;
testFilterProxyModel.foo = 1;
compare(testFilterProxyModel.count, 0);
compare(expressionFilter.w.count, 0);
wait(0);
compare(testFilterProxyModel.count, 1);
compare(expressionFilter.w.count, 1);
}
function test_directSorters() {
testSorterProxyModel.delayed = false;
testSorterProxyModel.foo = true;
compare(testSorterProxyModel.get(0).role1, 1);
expressionSorter.w.count = 0;
testSorterProxyModel.foo = false;
compare(testSorterProxyModel.get(0).role1, 2);
compare(expressionSorter.w.count, 6);
wait(0);
compare(testSorterProxyModel.get(0).role1, 2);
compare(expressionSorter.w.count, 6);
}
function test_delayedSorters() {
testSorterProxyModel.delayed = false;
testSorterProxyModel.foo = true;
compare(testSorterProxyModel.get(0).role1, 1);
testSorterProxyModel.delayed = true;
expressionSorter.w.count = 0;
testSorterProxyModel.foo = false;
testSorterProxyModel.foo = true;
testSorterProxyModel.foo = false;
compare(testSorterProxyModel.get(0).role1, 1);
compare(expressionSorter.w.count, 0);
wait(0);
compare(testSorterProxyModel.get(0).role1, 2);
compare(expressionSorter.w.count, 2);
}
function test_proxyRoles() {
// init not delayed
testRolesProxyModel.delayed = false;
testRolesProxyModel.foo = true;
compare(instantiator.object.foo, true);
expressionRole.w.count = 0;
dataChangedSpy.clear();
// test not delayed
testRolesProxyModel.foo = false;
compare(instantiator.object.foo, false);
compare(dataChangedSpy.count, 3);
var notDelayedCount = expressionRole.w.count; // why is it 12 and not just 3 ?
wait(0);
compare(instantiator.object.foo, false);
compare(dataChangedSpy.count, 3);
compare(expressionRole.w.count, notDelayedCount);
// init delayed
testRolesProxyModel.delayed = true;
expressionRole.w.count = 0;
dataChangedSpy.clear();
// test delayed
testRolesProxyModel.foo = true;
testRolesProxyModel.foo = false;
testRolesProxyModel.foo = true;
compare(instantiator.object.foo, false);
compare(dataChangedSpy.count, 0);
compare(expressionRole.w.count, 0);
wait(0);
compare(instantiator.object.foo, true);
compare(dataChangedSpy.count, 1);
var expectedDelayedCount = notDelayedCount / 3;
compare(expressionRole.w.count, expectedDelayedCount);
}
}
}