From 314598ae547dcd15351000cfd1a7f3c441e93588 Mon Sep 17 00:00:00 2001 From: grecko Date: Sat, 1 Feb 2020 14:35:14 +0100 Subject: [PATCH] feat: Add FilterContainer and SorterContainer attached types feat: Add FilterContainer and SorterContainer attached types --- filters/filtercontainer.cpp | 51 ++++++++++++++++ filters/filtercontainer.h | 26 +++++++++ filters/filtersqmltypes.cpp | 1 + sorters/sortercontainer.cpp | 51 ++++++++++++++++ sorters/sortercontainer.h | 26 +++++++++ sorters/sortersqmltypes.cpp | 2 + tests/SortFilterProxyModel.pro | 8 +-- tests/tst_filtercontainerattached.qml | 57 ++++++++++++++++++ tests/tst_sortercontainerattached.qml | 84 +++++++++++++++++++++++++++ 9 files changed, 302 insertions(+), 4 deletions(-) create mode 100644 tests/tst_filtercontainerattached.qml create mode 100644 tests/tst_sortercontainerattached.qml diff --git a/filters/filtercontainer.cpp b/filters/filtercontainer.cpp index cd85cb0..7a7e2ab 100644 --- a/filters/filtercontainer.cpp +++ b/filters/filtercontainer.cpp @@ -1,4 +1,6 @@ #include "filtercontainer.h" +#include "filter.h" +#include namespace qqsfpm { @@ -66,4 +68,53 @@ void FilterContainer::clear_filters(QQmlListProperty *list) that->clearFilters(); } +FilterContainerAttached::FilterContainerAttached(QObject* object) : QObject(object), + m_filter(qobject_cast(object)) +{ + if (!m_filter) + qmlWarning(object) << "FilterContainer must be attached to a Filter"; +} + +FilterContainerAttached::~FilterContainerAttached() +{ + if (m_filter && m_container) { + FilterContainer* container = qobject_cast(m_container.data()); + container->removeFilter(m_filter); + } +} + +/*! + \qmlattachedproperty bool FilterContainer::container + This attached property allows you to include in a \l FilterContainer a \l Filter that + has been instantiated outside of the \l FilterContainer, for example in an Instantiator. +*/ +QObject* FilterContainerAttached::container() const +{ + return m_container; +} + +void FilterContainerAttached::setContainer(QObject* object) +{ + if (m_container == object) + return; + + FilterContainer* container = qobject_cast(object); + if (object && !container) + qmlWarning(parent()) << "container must inherits from FilterContainer, " << object->metaObject()->className() << " provided"; + + if (m_container && m_filter) + qobject_cast(m_container.data())->removeFilter(m_filter); + + m_container = container ? object : nullptr; + if (container && m_filter) + container->appendFilter(m_filter); + + Q_EMIT containerChanged(); +} + +FilterContainerAttached* FilterContainerAttached::qmlAttachedProperties(QObject* object) +{ + return new FilterContainerAttached(object); +} + } diff --git a/filters/filtercontainer.h b/filters/filtercontainer.h index 6f1257a..1b7af80 100644 --- a/filters/filtercontainer.h +++ b/filters/filtercontainer.h @@ -3,6 +3,8 @@ #include #include +#include +#include namespace qqsfpm { @@ -34,9 +36,33 @@ private: static void clear_filters(QQmlListProperty* list); }; +class FilterContainerAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QObject* container READ container WRITE setContainer NOTIFY containerChanged) + +public: + FilterContainerAttached(QObject* object); + ~FilterContainerAttached(); + + QObject* container() const; + void setContainer(QObject* object); + + static FilterContainerAttached* qmlAttachedProperties(QObject* object); + +Q_SIGNALS: + void containerChanged(); + +private: + QPointer m_container = nullptr; + Filter* m_filter = nullptr; +}; + } #define FilterContainer_iid "fr.grecko.SortFilterProxyModel.FilterContainer" Q_DECLARE_INTERFACE(qqsfpm::FilterContainer, FilterContainer_iid) +QML_DECLARE_TYPEINFO(qqsfpm::FilterContainerAttached, QML_HAS_ATTACHED_PROPERTIES) + #endif // FILTERCONTAINER_H diff --git a/filters/filtersqmltypes.cpp b/filters/filtersqmltypes.cpp index 253221e..6704472 100644 --- a/filters/filtersqmltypes.cpp +++ b/filters/filtersqmltypes.cpp @@ -20,6 +20,7 @@ void registerFiltersTypes() { qmlRegisterType("SortFilterProxyModel", 0, 2, "ExpressionFilter"); qmlRegisterType("SortFilterProxyModel", 0, 2, "AnyOf"); qmlRegisterType("SortFilterProxyModel", 0, 2, "AllOf"); + qmlRegisterUncreatableType("SortFilterProxyModel", 0, 2, "FilterContainer", "FilterContainer can only be used as an attaching type"); } Q_COREAPP_STARTUP_FUNCTION(registerFiltersTypes) diff --git a/sorters/sortercontainer.cpp b/sorters/sortercontainer.cpp index d7b6740..36b1c62 100644 --- a/sorters/sortercontainer.cpp +++ b/sorters/sortercontainer.cpp @@ -1,4 +1,6 @@ #include "sortercontainer.h" +#include "sorter.h" +#include namespace qqsfpm { @@ -66,4 +68,53 @@ void SorterContainer::clear_sorters(QQmlListProperty *list) that->clearSorters(); } +SorterContainerAttached::SorterContainerAttached(QObject* object) : QObject(object), + m_sorter(qobject_cast(object)) +{ + if (!m_sorter) + qmlWarning(object) << "SorterContainerAttached must be attached to a Sorter"; +} + +SorterContainerAttached::~SorterContainerAttached() +{ + if (m_sorter && m_container) { + SorterContainer* container = qobject_cast(m_container.data()); + container->removeSorter(m_sorter); + } +} + +/*! + \qmlattachedproperty bool SorterContainer::container + This attached property allows you to include in a \l SorterContainer a \l Sorter that + has been instantiated outside of the \l SorterContainer, for example in an Instantiator. +*/ +QObject* SorterContainerAttached::container() const +{ + return m_container; +} + +void SorterContainerAttached::setContainer(QObject* object) +{ + if (m_container == object) + return; + + SorterContainer* container = qobject_cast(object); + if (object && !container) + qmlWarning(parent()) << "container must inherits from SorterContainer, " << object->metaObject()->className() << " provided"; + + if (m_container && m_sorter) + qobject_cast(m_container.data())->removeSorter(m_sorter); + + m_container = container ? object : nullptr; + if (container && m_sorter) + container->appendSorter(m_sorter); + + Q_EMIT containerChanged(); +} + +SorterContainerAttached* SorterContainerAttached::qmlAttachedProperties(QObject* object) +{ + return new SorterContainerAttached(object); +} + } diff --git a/sorters/sortercontainer.h b/sorters/sortercontainer.h index 5a09c09..e4c8e53 100644 --- a/sorters/sortercontainer.h +++ b/sorters/sortercontainer.h @@ -3,6 +3,8 @@ #include #include +#include +#include namespace qqsfpm { @@ -34,9 +36,33 @@ private: static void clear_sorters(QQmlListProperty* list); }; +class SorterContainerAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QObject* container READ container WRITE setContainer NOTIFY containerChanged) + +public: + SorterContainerAttached(QObject* object); + ~SorterContainerAttached(); + + QObject* container() const; + void setContainer(QObject* object); + + static SorterContainerAttached* qmlAttachedProperties(QObject* object); + +Q_SIGNALS: + void containerChanged(); + +private: + QPointer m_container = nullptr; + Sorter* m_sorter = nullptr; +}; + } #define SorterContainer_iid "fr.grecko.SortFilterProxyModel.SorterContainer" Q_DECLARE_INTERFACE(qqsfpm::SorterContainer, SorterContainer_iid) +QML_DECLARE_TYPEINFO(qqsfpm::SorterContainerAttached, QML_HAS_ATTACHED_PROPERTIES) + #endif // SORTERSSORTERCONTAINER_H diff --git a/sorters/sortersqmltypes.cpp b/sorters/sortersqmltypes.cpp index b1e377b..ceba423 100644 --- a/sorters/sortersqmltypes.cpp +++ b/sorters/sortersqmltypes.cpp @@ -3,6 +3,7 @@ #include "stringsorter.h" #include "filtersorter.h" #include "expressionsorter.h" +#include "sortercontainer.h" #include #include @@ -14,6 +15,7 @@ void registerSorterTypes() { qmlRegisterType("SortFilterProxyModel", 0, 2, "StringSorter"); qmlRegisterType("SortFilterProxyModel", 0, 2, "FilterSorter"); qmlRegisterType("SortFilterProxyModel", 0, 2, "ExpressionSorter"); + qmlRegisterUncreatableType("SortFilterProxyModel", 0, 2, "SorterContainer", "SorterContainer can only be used as an attaching type"); } Q_COREAPP_STARTUP_FUNCTION(registerSorterTypes) diff --git a/tests/SortFilterProxyModel.pro b/tests/SortFilterProxyModel.pro index 8c708ce..69fd583 100644 --- a/tests/SortFilterProxyModel.pro +++ b/tests/SortFilterProxyModel.pro @@ -26,11 +26,11 @@ OTHER_FILES += \ tst_proxyroles.qml \ tst_joinrole.qml \ tst_switchrole.qml \ - tst_expressionrole.qml - -DISTFILES += \ + tst_expressionrole.qml \ + tst_filtercontainerattached.qml \ tst_filtercontainers.qml \ tst_regexprole.qml \ tst_filtersorter.qml \ tst_filterrole.qml \ - tst_delayed.qml + tst_delayed.qml \ + tst_sortercontainerattached.qml diff --git a/tests/tst_filtercontainerattached.qml b/tests/tst_filtercontainerattached.qml new file mode 100644 index 0000000..03ac0fa --- /dev/null +++ b/tests/tst_filtercontainerattached.qml @@ -0,0 +1,57 @@ +import QtQuick 2.0 +import SortFilterProxyModel 0.2 +import QtQml.Models 2.2 +import QtQml 2.2 +import QtTest 1.1 + +Item { + + ListModel { + id: dataModel + ListElement { a: 0; b: 0; c: 0 } + ListElement { a: 0; b: 0; c: 1 } + ListElement { a: 0; b: 1; c: 0 } + ListElement { a: 0; b: 1; c: 1 } + ListElement { a: 1; b: 0; c: 0 } + ListElement { a: 1; b: 0; c: 1 } + ListElement { a: 1; b: 1; c: 0 } + ListElement { a: 1; b: 1; c: 1 } + } + + SortFilterProxyModel { + id: testModel + sourceModel: dataModel + } + + Instantiator { + id: filterInstantiator + model: ["a", "b", "c"] + delegate: ValueFilter { + FilterContainer.container: testModel + roleName: modelData + value: 1 + } + } + + TestCase { + name: "FilterContainerAttached" + + function modelValues() { + var modelValues = []; + + for (var i = 0; i < testModel.count; i++) + modelValues.push(testModel.get(i)); + + return modelValues; + } + + function test_filterContainers() { + compare(filterInstantiator.count, 3); + compare(modelValues(), [ { a: 1, b: 1, c: 1 }]); + filterInstantiator.model = ["a", "b"]; + wait(0); + compare(filterInstantiator.count, 2) + compare(modelValues(), [ { a: 1, b: 1, c: 0 }, { a: 1, b: 1, c: 1 }]); + } + } +} diff --git a/tests/tst_sortercontainerattached.qml b/tests/tst_sortercontainerattached.qml new file mode 100644 index 0000000..bb0acf0 --- /dev/null +++ b/tests/tst_sortercontainerattached.qml @@ -0,0 +1,84 @@ +import QtQuick 2.0 +import SortFilterProxyModel 0.2 +import QtQml.Models 2.2 +import QtQml 2.2 +import QtTest 1.1 + +Item { + + ListModel { + id: dataModel + ListElement { a: 3; b: 2; c: 9 } + ListElement { a: 3; b: 5; c: 0 } + ListElement { a: 3; b: 2; c: 8 } + ListElement { a: 2; b: 9; c: 1 } + ListElement { a: 2; b: 1; c: 7 } + ListElement { a: 2; b: 6; c: 2 } + ListElement { a: 1; b: 8; c: 6 } + ListElement { a: 1; b: 7; c: 3 } + ListElement { a: 1; b: 8; c: 5 } + } + + SortFilterProxyModel { + id: testModel + sourceModel: dataModel + } + + ListModel { + id: sorterRoleModel + ListElement { roleName: "a" } + ListElement { roleName: "b" } + ListElement { roleName: "c" } + } + + Instantiator { + id: sorterInstantiator + model: sorterRoleModel + delegate: RoleSorter { + SorterContainer.container: testModel + roleName: model.roleName + } + } + + TestCase { + name: "SorterContainerAttached" + + function modelValues() { + var modelValues = []; + + for (var i = 0; i < testModel.count; i++) + modelValues.push(testModel.get(i)); + + return modelValues; + } + + function test_sorterContainers() { + compare(sorterInstantiator.count, 3); + compare(modelValues(), [ + { a: 1, b: 7, c: 3 }, + { a: 1, b: 8, c: 5 }, + { a: 1, b: 8, c: 6 }, + { a: 2, b: 1, c: 7 }, + { a: 2, b: 6, c: 2 }, + { a: 2, b: 9, c: 1 }, + { a: 3, b: 2, c: 8 }, + { a: 3, b: 2, c: 9 }, + { a: 3, b: 5, c: 0 } + ]); + sorterRoleModel.remove(0); // a, b, c --> b, c + wait(0); + compare(sorterInstantiator.count, 2); + compare(JSON.stringify(modelValues()), JSON.stringify([ + { a: 2, b: 1, c: 7 }, + { a: 3, b: 2, c: 8 }, + { a: 3, b: 2, c: 9 }, + { a: 3, b: 5, c: 0 }, + { a: 2, b: 6, c: 2 }, + { a: 1, b: 7, c: 3 }, + { a: 1, b: 8, c: 5 }, + { a: 1, b: 8, c: 6 }, + { a: 2, b: 9, c: 1 }, + ])); + } + } +}