From 0c5917ee5b4a3b0d200beb6bf790509c08de5d05 Mon Sep 17 00:00:00 2001 From: Olaf Mandel Date: Tue, 27 Oct 2020 16:47:31 +0100 Subject: [PATCH 01/17] Implement own lessThan(QVariant, QVariant) Qt 5.15 deprecated the operator<(QVariant, QVariant). Do get rid of the warning and to prepare for the eventual removal of the operator, implement our own needs in a lessThan() function. The function is based on the description of QSortFilterProxyModel::lessThan(). Fixes #77 --- CMakeLists.txt | 1 + SortFilterProxyModel.pri | 2 + filters/rangefilter.cpp | 7 +++- qvariantlessthan.cpp | 79 ++++++++++++++++++++++++++++++++++++++++ qvariantlessthan.h | 12 ++++++ sorters/rolesorter.cpp | 5 ++- 6 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 qvariantlessthan.cpp create mode 100644 qvariantlessthan.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d3bf2b..7b4e1f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) # This is to find generated *.moc and *.h file add_library(SortFilterProxyModel OBJECT qqmlsortfilterproxymodel.cpp + qvariantlessthan.cpp filters/filter.cpp filters/filtercontainer.cpp filters/rolefilter.cpp diff --git a/SortFilterProxyModel.pri b/SortFilterProxyModel.pri index 22f7a83..c5c5dd4 100644 --- a/SortFilterProxyModel.pri +++ b/SortFilterProxyModel.pri @@ -3,6 +3,7 @@ INCLUDEPATH += $$PWD HEADERS += $$PWD/qqmlsortfilterproxymodel.h \ + $$PWD/qvariantlessthan.h \ $$PWD/filters/filter.h \ $$PWD/filters/filtercontainer.h \ $$PWD/filters/rolefilter.h \ @@ -30,6 +31,7 @@ HEADERS += $$PWD/qqmlsortfilterproxymodel.h \ $$PWD/proxyroles/filterrole.h SOURCES += $$PWD/qqmlsortfilterproxymodel.cpp \ + $$PWD/qvariantlessthan.cpp \ $$PWD/filters/filter.cpp \ $$PWD/filters/filtercontainer.cpp \ $$PWD/filters/rolefilter.cpp \ diff --git a/filters/rangefilter.cpp b/filters/rangefilter.cpp index 2a6fde2..ce683a5 100644 --- a/filters/rangefilter.cpp +++ b/filters/rangefilter.cpp @@ -1,4 +1,5 @@ #include "rangefilter.h" +#include "qvariantlessthan.h" namespace qqsfpm { @@ -130,9 +131,11 @@ bool RangeFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilter { QVariant value = sourceData(sourceIndex, proxyModel); bool lessThanMin = m_minimumValue.isValid() && - (m_minimumInclusive ? value < m_minimumValue : value <= m_minimumValue); + (m_minimumInclusive ? qqsfpm::lessThan(value, m_minimumValue) + : !qqsfpm::lessThan(m_minimumValue, value)); bool moreThanMax = m_maximumValue.isValid() && - (m_maximumInclusive ? value > m_maximumValue : value >= m_maximumValue); + (m_maximumInclusive ? qqsfpm::lessThan(m_maximumValue, value) + : !qqsfpm::lessThan(value, m_maximumValue)); return !(lessThanMin || moreThanMax); } diff --git a/qvariantlessthan.cpp b/qvariantlessthan.cpp new file mode 100644 index 0000000..d93b6fe --- /dev/null +++ b/qvariantlessthan.cpp @@ -0,0 +1,79 @@ +#include "qvariantlessthan.h" + +#include + +namespace qqsfpm { + +/*! + \brief Less-than operator for generic QVariants + + Since Qt 5.15 deprecated the less-than operator of QVariant, we + have to provide our own implementation. On older Qt versions, + use the original implementation. + + Includes special implementations for numberic types, char, date and + time. Everything else is converted to String and compared then. +*/ +bool lessThan(const QVariant &lhs, const QVariant &rhs) +{ +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + return lhs < rhs; +#else + static const auto numericTypes = QVector{ + QMetaType::Int, + QMetaType::UInt, + QMetaType::LongLong, + QMetaType::ULongLong, + QMetaType::Float, + QMetaType::Double, + }; + static const auto unsignedTypes = QVector{ + QMetaType::UInt, + QMetaType::ULongLong, + }; + static const auto dateTimeTypes = QVector{ + QMetaType::QDate, + QMetaType::QTime, + QMetaType::QDateTime, + }; + + const auto lt = static_cast(lhs.type()); + const auto rt = static_cast(rhs.type()); + if (numericTypes.contains(lt) && numericTypes.contains(rt)) { + if (lt == QMetaType::Double || lt == QMetaType::Float + || rt == QMetaType::Double || rt == QMetaType::Float) { + return lhs.toDouble() < rhs.toDouble(); + } else { + const auto ul = unsignedTypes.contains(lt); + const auto ur = unsignedTypes.contains(rt); + if (ul && ur) { + return lhs.toULongLong() < rhs.toULongLong(); + } else if (!ul && !ur) { + return lhs.toLongLong() < rhs.toLongLong(); + } else if (ul) { + const auto r = rhs.toLongLong(); + return r > 0 && + lhs.toULongLong() < static_cast(r); + } else { + const auto l = lhs.toLongLong(); + return l < 0 || + static_cast(l) < rhs.toULongLong(); + } + } + } else if (dateTimeTypes.contains(lt) && dateTimeTypes.contains(rt)) { + if (lt == QMetaType::QDate && rt == QMetaType::QDate) { + return lhs.toDate() < rhs.toDate(); + } else if (lt == QMetaType::QTime && rt == QMetaType::QTime) { + return lhs.toTime() < rhs.toTime(); + } else { + return lhs.toDateTime() < rhs.toDateTime(); + } + } else if (lt == QMetaType::Char && rt == QMetaType::Char) { + return lhs.toChar() < rhs.toChar(); + } else { + return lhs.toString() < rhs.toString(); + } +#endif +} + +} // namespace qqsfpm diff --git a/qvariantlessthan.h b/qvariantlessthan.h new file mode 100644 index 0000000..6f6a572 --- /dev/null +++ b/qvariantlessthan.h @@ -0,0 +1,12 @@ +#ifndef QVARIANTLESSTHAN_H +#define QVARIANTLESSTHAN_H + +#include + +namespace qqsfpm { + +bool lessThan(const QVariant &lhs, const QVariant &rhs); + +} // namespace qqsfpm + +#endif // QVARIANTLESSTHAN_H diff --git a/sorters/rolesorter.cpp b/sorters/rolesorter.cpp index db2d446..4f6b2c3 100644 --- a/sorters/rolesorter.cpp +++ b/sorters/rolesorter.cpp @@ -1,5 +1,6 @@ #include "rolesorter.h" #include "qqmlsortfilterproxymodel.h" +#include "qvariantlessthan.h" namespace qqsfpm { @@ -59,9 +60,9 @@ int RoleSorter::compare(const QModelIndex &sourceLeft, const QModelIndex& source QPair pair = sourceData(sourceLeft, sourceRight, proxyModel); QVariant leftValue = pair.first; QVariant rightValue = pair.second; - if (leftValue < rightValue) + if (qqsfpm::lessThan(leftValue, rightValue)) return -1; - if (leftValue > rightValue) + if (qqsfpm::lessThan(rightValue, leftValue)) return 1; return 0; } From fe502036da57e58c741243f815bf7669e16d7937 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Sat, 14 May 2022 17:58:24 +0800 Subject: [PATCH 02/17] Port to Qt 6 The PatternSyntax enum had to be removed as QRegularExpression only supports Perl-compatible regular expressions. As QVariant's comparison operators were removed, a simplified comparison is now done as a starting point, but should probably be extended to support more types. Fixes #84 Fixes #86 --- SortFilterProxyModel.pri | 6 ++-- SortFilterProxyModel.qbs | 2 ++ filters/filtercontainer.cpp | 4 +-- filters/filtercontainer.h | 4 +-- filters/rangefilter.cpp | 3 +- filters/regexpfilter.cpp | 47 +++++++----------------- filters/regexpfilter.h | 23 ++++-------- proxyroles/proxyrolecontainer.cpp | 4 +-- proxyroles/proxyrolecontainer.h | 4 +-- qqmlsortfilterproxymodel.cpp | 23 ++---------- qqmlsortfilterproxymodel.h | 16 +-------- sorters/rolesorter.cpp | 11 ++---- sorters/sortercontainer.cpp | 4 +-- sorters/sortercontainer.h | 4 +-- tests/BLACKLIST | 2 ++ tests/tst_rolesorter.qml | 9 +++-- tests/tst_stringsorter.qml | 9 +++-- utils/utils.cpp | 59 +++++++++++++++++++++++++++++++ utils/utils.h | 17 +++++++++ 19 files changed, 131 insertions(+), 120 deletions(-) create mode 100644 tests/BLACKLIST create mode 100644 utils/utils.cpp create mode 100644 utils/utils.h diff --git a/SortFilterProxyModel.pri b/SortFilterProxyModel.pri index 22f7a83..5c42037 100644 --- a/SortFilterProxyModel.pri +++ b/SortFilterProxyModel.pri @@ -27,7 +27,8 @@ HEADERS += $$PWD/qqmlsortfilterproxymodel.h \ $$PWD/proxyroles/singlerole.h \ $$PWD/proxyroles/regexprole.h \ $$PWD/sorters/filtersorter.h \ - $$PWD/proxyroles/filterrole.h + $$PWD/proxyroles/filterrole.h \ + $$PWD/utils/utils.h SOURCES += $$PWD/qqmlsortfilterproxymodel.cpp \ $$PWD/filters/filter.cpp \ @@ -57,4 +58,5 @@ SOURCES += $$PWD/qqmlsortfilterproxymodel.cpp \ $$PWD/proxyroles/singlerole.cpp \ $$PWD/proxyroles/regexprole.cpp \ $$PWD/sorters/filtersorter.cpp \ - $$PWD/proxyroles/filterrole.cpp + $$PWD/proxyroles/filterrole.cpp \ + $$PWD/utils/utils.cpp diff --git a/SortFilterProxyModel.qbs b/SortFilterProxyModel.qbs index 7be48f4..23ec847 100644 --- a/SortFilterProxyModel.qbs +++ b/SortFilterProxyModel.qbs @@ -57,6 +57,8 @@ Group { "sorters/sortersqmltypes.cpp", "sorters/stringsorter.cpp", "sorters/stringsorter.h", + "utils/utils.cpp", + "utils/utils.h", "qqmlsortfilterproxymodel.cpp", "qqmlsortfilterproxymodel.h" ] diff --git a/filters/filtercontainer.cpp b/filters/filtercontainer.cpp index 5bba02d..ad955e1 100644 --- a/filters/filtercontainer.cpp +++ b/filters/filtercontainer.cpp @@ -56,13 +56,13 @@ void FilterContainer::append_filter(QQmlListProperty* list, Filter* filt that->appendFilter(filter); } -int FilterContainer::count_filter(QQmlListProperty* list) +qsizetype FilterContainer::count_filter(QQmlListProperty* list) { QList* filters = static_cast*>(list->data); return filters->count(); } -Filter* FilterContainer::at_filter(QQmlListProperty* list, int index) +Filter* FilterContainer::at_filter(QQmlListProperty* list, qsizetype index) { QList* filters = static_cast*>(list->data); return filters->at(index); diff --git a/filters/filtercontainer.h b/filters/filtercontainer.h index 4fc06f3..9adf41f 100644 --- a/filters/filtercontainer.h +++ b/filters/filtercontainer.h @@ -31,8 +31,8 @@ class FilterContainer { virtual void onFiltersCleared() = 0; static void append_filter(QQmlListProperty* list, Filter* filter); - static int count_filter(QQmlListProperty* list); - static Filter* at_filter(QQmlListProperty* list, int index); + static qsizetype count_filter(QQmlListProperty* list); + static Filter* at_filter(QQmlListProperty* list, qsizetype index); static void clear_filters(QQmlListProperty* list); }; diff --git a/filters/rangefilter.cpp b/filters/rangefilter.cpp index 2a6fde2..b36adba 100644 --- a/filters/rangefilter.cpp +++ b/filters/rangefilter.cpp @@ -1,4 +1,5 @@ #include "rangefilter.h" +#include "../utils/utils.h" namespace qqsfpm { @@ -128,7 +129,7 @@ void RangeFilter::setMaximumInclusive(bool maximumInclusive) bool RangeFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const { - QVariant value = sourceData(sourceIndex, proxyModel); + const QVariant value = sourceData(sourceIndex, proxyModel); bool lessThanMin = m_minimumValue.isValid() && (m_minimumInclusive ? value < m_minimumValue : value <= m_minimumValue); bool moreThanMax = m_maximumValue.isValid() && diff --git a/filters/regexpfilter.cpp b/filters/regexpfilter.cpp index f308765..7c6b6f0 100644 --- a/filters/regexpfilter.cpp +++ b/filters/regexpfilter.cpp @@ -35,6 +35,12 @@ namespace qqsfpm { \sa syntax */ +RegExpFilter::RegExpFilter() : + m_caseSensitivity(m_regExp.patternOptions().testFlag( + QRegularExpression::CaseInsensitiveOption) ? Qt::CaseInsensitive : Qt::CaseSensitive) +{ +} + QString RegExpFilter::pattern() const { return m_pattern; @@ -51,38 +57,6 @@ void RegExpFilter::setPattern(const QString& pattern) invalidate(); } -/*! - \qmlproperty enum RegExpFilter::syntax - - The pattern used to filter the contents of the source model. - - Only the source model's value having their \l RoleFilter::roleName data matching this \l pattern with the specified \l syntax will be kept. - - \value RegExpFilter.RegExp A rich Perl-like pattern matching syntax. This is the default. - \value RegExpFilter.Wildcard This provides a simple pattern matching syntax similar to that used by shells (command interpreters) for "file globbing". - \value RegExpFilter.FixedString The pattern is a fixed string. This is equivalent to using the RegExp pattern on a string in which all metacharacters are escaped. - \value RegExpFilter.RegExp2 Like RegExp, but with greedy quantifiers. - \value RegExpFilter.WildcardUnix This is similar to Wildcard but with the behavior of a Unix shell. The wildcard characters can be escaped with the character "\". - \value RegExpFilter.W3CXmlSchema11 The pattern is a regular expression as defined by the W3C XML Schema 1.1 specification. - - \sa pattern -*/ -RegExpFilter::PatternSyntax RegExpFilter::syntax() const -{ - return m_syntax; -} - -void RegExpFilter::setSyntax(RegExpFilter::PatternSyntax syntax) -{ - if (m_syntax == syntax) - return; - - m_syntax = syntax; - m_regExp.setPatternSyntax(static_cast(syntax)); - Q_EMIT syntaxChanged(); - invalidate(); -} - /*! \qmlproperty Qt::CaseSensitivity RegExpFilter::caseSensitivity @@ -99,15 +73,18 @@ void RegExpFilter::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity) return; m_caseSensitivity = caseSensitivity; - m_regExp.setCaseSensitivity(caseSensitivity); + QRegularExpression::PatternOptions patternOptions = m_regExp.patternOptions(); + if (caseSensitivity == Qt::CaseInsensitive) + patternOptions.setFlag(QRegularExpression::CaseInsensitiveOption); + m_regExp.setPatternOptions(patternOptions); Q_EMIT caseSensitivityChanged(); invalidate(); } bool RegExpFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const { - QString string = sourceData(sourceIndex, proxyModel).toString(); - return m_regExp.indexIn(string) != -1; + const QString string = sourceData(sourceIndex, proxyModel).toString(); + return m_regExp.match(string).hasMatch(); } } diff --git a/filters/regexpfilter.h b/filters/regexpfilter.h index 2c20a6a..6594564 100644 --- a/filters/regexpfilter.h +++ b/filters/regexpfilter.h @@ -3,32 +3,23 @@ #include "rolefilter.h" +#include + namespace qqsfpm { class RegExpFilter : public RoleFilter { Q_OBJECT Q_PROPERTY(QString pattern READ pattern WRITE setPattern NOTIFY patternChanged) - Q_PROPERTY(PatternSyntax syntax READ syntax WRITE setSyntax NOTIFY syntaxChanged) Q_PROPERTY(Qt::CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity NOTIFY caseSensitivityChanged) public: - enum PatternSyntax { - RegExp = QRegExp::RegExp, - Wildcard = QRegExp::Wildcard, - FixedString = QRegExp::FixedString, - RegExp2 = QRegExp::RegExp2, - WildcardUnix = QRegExp::WildcardUnix, - W3CXmlSchema11 = QRegExp::W3CXmlSchema11 }; - Q_ENUMS(PatternSyntax) - using RoleFilter::RoleFilter; + RegExpFilter(); + QString pattern() const; void setPattern(const QString& pattern); - PatternSyntax syntax() const; - void setSyntax(PatternSyntax syntax); - Qt::CaseSensitivity caseSensitivity() const; void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity); @@ -37,13 +28,11 @@ class RegExpFilter : public RoleFilter { Q_SIGNALS: void patternChanged(); - void syntaxChanged(); void caseSensitivityChanged(); private: - QRegExp m_regExp; - Qt::CaseSensitivity m_caseSensitivity = m_regExp.caseSensitivity(); - PatternSyntax m_syntax = static_cast(m_regExp.patternSyntax()); + QRegularExpression m_regExp; + Qt::CaseSensitivity m_caseSensitivity; QString m_pattern = m_regExp.pattern(); }; diff --git a/proxyroles/proxyrolecontainer.cpp b/proxyroles/proxyrolecontainer.cpp index f8ea665..8418fec 100644 --- a/proxyroles/proxyrolecontainer.cpp +++ b/proxyroles/proxyrolecontainer.cpp @@ -43,13 +43,13 @@ void ProxyRoleContainer::append_proxyRole(QQmlListProperty* list, Pro that->appendProxyRole(proxyRole); } -int ProxyRoleContainer::count_proxyRole(QQmlListProperty* list) +qsizetype ProxyRoleContainer::count_proxyRole(QQmlListProperty* list) { QList* ProxyRoles = static_cast*>(list->data); return ProxyRoles->count(); } -ProxyRole* ProxyRoleContainer::at_proxyRole(QQmlListProperty* list, int index) +ProxyRole* ProxyRoleContainer::at_proxyRole(QQmlListProperty* list, qsizetype index) { QList* ProxyRoles = static_cast*>(list->data); return ProxyRoles->at(index); diff --git a/proxyroles/proxyrolecontainer.h b/proxyroles/proxyrolecontainer.h index bcd932e..415b8c0 100644 --- a/proxyroles/proxyrolecontainer.h +++ b/proxyroles/proxyrolecontainer.h @@ -29,8 +29,8 @@ class ProxyRoleContainer { virtual void onProxyRolesCleared() = 0; static void append_proxyRole(QQmlListProperty* list, ProxyRole* proxyRole); - static int count_proxyRole(QQmlListProperty* list); - static ProxyRole* at_proxyRole(QQmlListProperty* list, int index); + static qsizetype count_proxyRole(QQmlListProperty* list); + static ProxyRole* at_proxyRole(QQmlListProperty* list, qsizetype index); static void clear_proxyRoles(QQmlListProperty* list); }; diff --git a/qqmlsortfilterproxymodel.cpp b/qqmlsortfilterproxymodel.cpp index bd06435..6a3e77c 100644 --- a/qqmlsortfilterproxymodel.cpp +++ b/qqmlsortfilterproxymodel.cpp @@ -92,37 +92,20 @@ void QQmlSortFilterProxyModel::setFilterRoleName(const QString& filterRoleName) QString QQmlSortFilterProxyModel::filterPattern() const { - return filterRegExp().pattern(); + return filterRegularExpression().pattern(); } void QQmlSortFilterProxyModel::setFilterPattern(const QString& filterPattern) { - QRegExp regExp = filterRegExp(); + QRegularExpression regExp = filterRegularExpression(); if (regExp.pattern() == filterPattern) return; regExp.setPattern(filterPattern); - QSortFilterProxyModel::setFilterRegExp(regExp); + QSortFilterProxyModel::setFilterRegularExpression(regExp); Q_EMIT filterPatternChanged(); } -QQmlSortFilterProxyModel::PatternSyntax QQmlSortFilterProxyModel::filterPatternSyntax() const -{ - return static_cast(filterRegExp().patternSyntax()); -} - -void QQmlSortFilterProxyModel::setFilterPatternSyntax(QQmlSortFilterProxyModel::PatternSyntax patternSyntax) -{ - QRegExp regExp = filterRegExp(); - QRegExp::PatternSyntax patternSyntaxTmp = static_cast(patternSyntax); - if (regExp.patternSyntax() == patternSyntaxTmp) - return; - - regExp.setPatternSyntax(patternSyntaxTmp); - QSortFilterProxyModel::setFilterRegExp(regExp); - Q_EMIT filterPatternSyntaxChanged(); -} - const QVariant& QQmlSortFilterProxyModel::filterValue() const { return m_filterValue; diff --git a/qqmlsortfilterproxymodel.h b/qqmlsortfilterproxymodel.h index dbe0229..bf4f32e 100644 --- a/qqmlsortfilterproxymodel.h +++ b/qqmlsortfilterproxymodel.h @@ -26,7 +26,6 @@ class QQmlSortFilterProxyModel : public QSortFilterProxyModel, Q_PROPERTY(QString filterRoleName READ filterRoleName WRITE setFilterRoleName NOTIFY filterRoleNameChanged) Q_PROPERTY(QString filterPattern READ filterPattern WRITE setFilterPattern NOTIFY filterPatternChanged) - Q_PROPERTY(PatternSyntax filterPatternSyntax READ filterPatternSyntax WRITE setFilterPatternSyntax NOTIFY filterPatternSyntaxChanged) Q_PROPERTY(QVariant filterValue READ filterValue WRITE setFilterValue NOTIFY filterValueChanged) Q_PROPERTY(QString sortRoleName READ sortRoleName WRITE setSortRoleName NOTIFY sortRoleNameChanged) @@ -37,15 +36,6 @@ class QQmlSortFilterProxyModel : public QSortFilterProxyModel, Q_PROPERTY(QQmlListProperty proxyRoles READ proxyRolesListProperty) public: - enum PatternSyntax { - RegExp = QRegExp::RegExp, - Wildcard = QRegExp::Wildcard, - FixedString = QRegExp::FixedString, - RegExp2 = QRegExp::RegExp2, - WildcardUnix = QRegExp::WildcardUnix, - W3CXmlSchema11 = QRegExp::W3CXmlSchema11 }; - Q_ENUMS(PatternSyntax) - QQmlSortFilterProxyModel(QObject* parent = 0); int count() const; @@ -59,9 +49,6 @@ class QQmlSortFilterProxyModel : public QSortFilterProxyModel, QString filterPattern() const; void setFilterPattern(const QString& filterPattern); - PatternSyntax filterPatternSyntax() const; - void setFilterPatternSyntax(PatternSyntax patternSyntax); - const QVariant& filterValue() const; void setFilterValue(const QVariant& filterValue); @@ -97,7 +84,6 @@ class QQmlSortFilterProxyModel : public QSortFilterProxyModel, void delayedChanged(); void filterRoleNameChanged(); - void filterPatternSyntaxChanged(); void filterPatternChanged(); void filterValueChanged(); @@ -109,7 +95,7 @@ class QQmlSortFilterProxyModel : public QSortFilterProxyModel, bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; protected Q_SLOTS: - void resetInternalData(); + void resetInternalData() override; private Q_SLOTS: void queueInvalidateFilter(); diff --git a/sorters/rolesorter.cpp b/sorters/rolesorter.cpp index db2d446..d98e9b1 100644 --- a/sorters/rolesorter.cpp +++ b/sorters/rolesorter.cpp @@ -1,5 +1,6 @@ #include "rolesorter.h" #include "qqmlsortfilterproxymodel.h" +#include "../utils/utils.h" namespace qqsfpm { @@ -56,14 +57,8 @@ QPair RoleSorter::sourceData(const QModelIndex &sourceLeft, int RoleSorter::compare(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const { - QPair pair = sourceData(sourceLeft, sourceRight, proxyModel); - QVariant leftValue = pair.first; - QVariant rightValue = pair.second; - if (leftValue < rightValue) - return -1; - if (leftValue > rightValue) - return 1; - return 0; + const QPair pair = sourceData(sourceLeft, sourceRight, proxyModel); + return compareVariants(pair.first, pair.second); } } diff --git a/sorters/sortercontainer.cpp b/sorters/sortercontainer.cpp index f986e37..5255e29 100644 --- a/sorters/sortercontainer.cpp +++ b/sorters/sortercontainer.cpp @@ -56,13 +56,13 @@ void SorterContainer::append_sorter(QQmlListProperty* list, Sorter* sort that->appendSorter(sorter); } -int SorterContainer::count_sorter(QQmlListProperty* list) +qsizetype SorterContainer::count_sorter(QQmlListProperty* list) { QList* sorters = static_cast*>(list->data); return sorters->count(); } -Sorter* SorterContainer::at_sorter(QQmlListProperty* list, int index) +Sorter* SorterContainer::at_sorter(QQmlListProperty* list, qsizetype index) { QList* sorters = static_cast*>(list->data); return sorters->at(index); diff --git a/sorters/sortercontainer.h b/sorters/sortercontainer.h index 016cc6d..c60a067 100644 --- a/sorters/sortercontainer.h +++ b/sorters/sortercontainer.h @@ -31,8 +31,8 @@ class SorterContainer { virtual void onSortersCleared() = 0; static void append_sorter(QQmlListProperty* list, Sorter* sorter); - static int count_sorter(QQmlListProperty* list); - static Sorter* at_sorter(QQmlListProperty* list, int index); + static qsizetype count_sorter(QQmlListProperty* list); + static Sorter* at_sorter(QQmlListProperty* list, qsizetype index); static void clear_sorters(QQmlListProperty* list); }; diff --git a/tests/BLACKLIST b/tests/BLACKLIST new file mode 100644 index 0000000..f881ba0 --- /dev/null +++ b/tests/BLACKLIST @@ -0,0 +1,2 @@ +[StringSorterTests::test_stringSorters:doNotIgnorePunctuation] +macos diff --git a/tests/tst_rolesorter.qml b/tests/tst_rolesorter.qml index f082227..19bfde2 100644 --- a/tests/tst_rolesorter.qml +++ b/tests/tst_rolesorter.qml @@ -60,12 +60,11 @@ Item { verify(testModel.count === sorter.expectedValues.length, "Expected count " + sorter.expectedValues.length + ", actual count: " + testModel.count); - for (var i = 0; i < testModel.count; i++) - { - var modelValue = testModel.get(i, sorter.roleName); - verify(modelValue === sorter.expectedValues[i], - "Expected testModel value " + sorter.expectedValues[i] + ", actual: " + modelValue); + let actualValues = []; + for (var i = 0; i < testModel.count; i++) { + actualValues.push(testModel.get(i, sorter.roleName)); } + compare(actualValues, sorter.expectedValues); } } } diff --git a/tests/tst_stringsorter.qml b/tests/tst_stringsorter.qml index f4d4ea9..3e22c65 100644 --- a/tests/tst_stringsorter.qml +++ b/tests/tst_stringsorter.qml @@ -75,12 +75,11 @@ Item { verify(testModel.count === sorter.expectedValues.length, "Expected count " + sorter.expectedValues.length + ", actual count: " + testModel.count); - for (var i = 0; i < testModel.count; i++) - { - var modelValue = testModel.get(i, sorter.roleName); - verify(modelValue === sorter.expectedValues[i], - "Expected testModel value " + sorter.expectedValues[i] + ", actual: " + modelValue); + let actualValues = []; + for (var i = 0; i < testModel.count; i++) { + actualValues.push(testModel.get(i, sorter.roleName)); } + compare(actualValues, sorter.expectedValues); } } } diff --git a/utils/utils.cpp b/utils/utils.cpp new file mode 100644 index 0000000..e32f509 --- /dev/null +++ b/utils/utils.cpp @@ -0,0 +1,59 @@ +#include "utils.h" + +#include + +namespace qqsfpm { + +int compareVariants(const QVariant &lhs, const QVariant &rhs) +{ + // Do the QString check first because otherwise the canConvert check will get hit for strings. + if (lhs.typeId() == QMetaType::QString && rhs.typeId() == QMetaType::QString) { + const auto lhsValue = lhs.toString(); + const auto rhsValue = rhs.toString(); + if (lhsValue == rhsValue) + return 0; + return lhsValue.compare(rhsValue, Qt::CaseInsensitive); + } else if (lhs.typeId() == QMetaType::Bool && rhs.typeId() == QMetaType::Bool) { + const auto lhsValue = lhs.toBool(); + const auto rhsValue = rhs.toBool(); + if (lhsValue == rhsValue) + return 0; + // false < true. + return !lhsValue ? -1 : 1; + } else if (lhs.typeId() == QMetaType::QDate && rhs.typeId() == QMetaType::QDate) { + const auto lhsValue = lhs.toDate(); + const auto rhsValue = rhs.toDate(); + if (lhsValue == rhsValue) + return 0; + return lhsValue < rhsValue ? -1 : 1; + } else if (lhs.typeId() == QMetaType::QDateTime && rhs.typeId() == QMetaType::QDateTime) { + const auto lhsValue = lhs.toDateTime(); + const auto rhsValue = rhs.toDateTime(); + if (lhsValue == rhsValue) + return 0; + return lhsValue < rhsValue ? -1 : 1; + } else if (lhs.typeId() == QMetaType::QStringList && rhs.typeId() == QMetaType::QStringList) { + const auto lhsValue = lhs.toStringList(); + const auto rhsValue = rhs.toStringList(); + if (lhsValue == rhsValue) + return 0; + return lhsValue < rhsValue ? -1 : 1; + } else if (lhs.canConvert() && rhs.canConvert()) { + const auto lhsValue = lhs.toInt(); + const auto rhsValue = rhs.toInt(); + if (lhsValue == rhsValue) + return 0; + return lhsValue < rhsValue ? -1 : 1; + } else if (lhs.canConvert() && rhs.canConvert()) { + const auto lhsValue = lhs.toReal(); + const auto rhsValue = rhs.toReal(); + if (qFuzzyCompare(lhsValue, rhsValue)) + return 0; + return lhsValue < rhsValue ? -1 : 1; + } + + qWarning() << "Don't know how to compare" << lhs << "against" << rhs << "- returning 0"; + return 0; +} + +} diff --git a/utils/utils.h b/utils/utils.h new file mode 100644 index 0000000..199c57b --- /dev/null +++ b/utils/utils.h @@ -0,0 +1,17 @@ +#ifndef UTILS_H +#define UTILS_H + +#include + +namespace qqsfpm { + +int compareVariants(const QVariant &lhs, const QVariant &rhs); + +inline bool operator<(const QVariant &lhs, const QVariant &rhs) { return compareVariants(lhs, rhs) < 0; } +inline bool operator<=(const QVariant &lhs, const QVariant &rhs) { return compareVariants(lhs, rhs) <= 0; } +inline bool operator>(const QVariant &lhs, const QVariant &rhs) { return compareVariants(lhs, rhs) > 0; } +inline bool operator>=(const QVariant &lhs, const QVariant &rhs) { return compareVariants(lhs, rhs) >= 0; } + +} + +#endif // UTILS_H From 1873b489ba3debd4fd4b8ad06f4552c1181249fb Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Sat, 9 Jul 2022 15:44:44 +0800 Subject: [PATCH 03/17] Update CMakeListst.txt for Qt 6 --- CMakeLists.txt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d3bf2b..5c18d33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.14) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) -find_package(Qt5 REQUIRED +find_package(Qt6 REQUIRED COMPONENTS Core Qml - ) +) set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) # This is to find generated *.moc and *.h files in build dir @@ -40,10 +40,12 @@ add_library(SortFilterProxyModel OBJECT proxyroles/regexprole.cpp sorters/filtersorter.cpp proxyroles/filterrole.cpp - ) + utils/utils.cpp + utils/utils.h +) target_include_directories(SortFilterProxyModel PUBLIC ${CMAKE_CURRENT_LIST_DIR} - $ - $ - ) + $ + $ +) From f336b3cdbbfdc59c8689bc9a72f13b4b78c142f1 Mon Sep 17 00:00:00 2001 From: Andreas Traczyk Date: Wed, 3 Aug 2022 15:50:12 -0400 Subject: [PATCH 04/17] cmake: fix c++17 build requirement for msc --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c18d33..e2d9e49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,10 @@ add_library(SortFilterProxyModel OBJECT utils/utils.h ) +if ((MSVC) AND (MSVC_VERSION GREATER_EQUAL 1914)) + target_compile_options(SortFilterProxyModel PUBLIC "/Zc:__cplusplus") +endif() + target_include_directories(SortFilterProxyModel PUBLIC ${CMAKE_CURRENT_LIST_DIR} $ From f2881493e42bd7b7d5b7abe804dad084dd610b71 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Thu, 18 Aug 2022 16:51:26 +0800 Subject: [PATCH 05/17] Fix build with ASAN on Linux I was getting the following error: /usr/bin/ld: isle/3rdparty/SortFilterProxyModel/CMakeFiles/SortFilterProxyModel.dir/SortFilterProxyModel_autogen/mocs_compilation.cpp.o: relocation R_X86_64_PC32 against symbol `__asan_option_detect_stack_use_after_return' can not be used when making a shared object; recompile with -fPIC And: /home/mitch/dev/bgv/isle/3rdparty/SortFilterProxyModel/qqmlsortfilterproxymodel.cpp:1: error: In included file: "You must build your code with position independent code if Qt was configured with -reduce-relocations. " "Compile your code with -fPIC (and not with -fPIE)." Initially I fixed this with: set(CMAKE_POSITION_INDEPENDENT_CODE ON) but was told the proper solution is to add the missing target_link_libraries. --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index e2d9e49..8095071 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,12 @@ add_library(SortFilterProxyModel OBJECT utils/utils.h ) +target_link_libraries(SortFilterProxyModel + PRIVATE + Qt6::Core + Qt6::Qml +) + if ((MSVC) AND (MSVC_VERSION GREATER_EQUAL 1914)) target_compile_options(SortFilterProxyModel PUBLIC "/Zc:__cplusplus") endif() From 0a6e205adba92c51ea411053a7c7f0b70de500d7 Mon Sep 17 00:00:00 2001 From: Arno Rehn Date: Tue, 9 Feb 2021 15:37:37 +0100 Subject: [PATCH 06/17] ExpressionRole: Apply source-get optimization and possibly cache result --- proxyroles/expressionrole.cpp | 67 +++++++++++++++++++++++------------ proxyroles/expressionrole.h | 12 +++++++ 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/proxyroles/expressionrole.cpp b/proxyroles/expressionrole.cpp index 70af4fa..6a589d3 100644 --- a/proxyroles/expressionrole.cpp +++ b/proxyroles/expressionrole.cpp @@ -62,22 +62,40 @@ void ExpressionRole::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyMo updateContext(proxyModel); } -QVariant ExpressionRole::data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) +bool ExpressionRole::isCached() const { - if (!m_scriptString.isEmpty()) { - QVariantMap modelMap; - QHash roles = proxyModel.roleNames(); + return m_cached; +} - QQmlContext context(qmlContext(this)); - auto addToContext = [&] (const QString &name, const QVariant& value) { - context.setContextProperty(name, value); - modelMap.insert(name, value); - }; +void ExpressionRole::setCached(bool cached) +{ + if (m_cached == cached) { + return; + } + + m_cached = cached; + emit cachedChanged(m_cached); +} + +void ExpressionRole::invalidateCache() +{ + m_cache.clear(); +} - for (auto it = roles.cbegin(); it != roles.cend(); ++it) - addToContext(it.value(), proxyModel.sourceData(sourceIndex, it.key())); - addToContext("index", sourceIndex.row()); +QVariant ExpressionRole::data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) +{ + if (isCached()) { + const auto it = m_cache.constFind(sourceIndex); + if (it != m_cache.constEnd()) { + return *it; + } + } + if (!m_scriptString.isEmpty()) { + const QVariant modelMap = proxyModel.sourceData(sourceIndex); + QQmlContext context(qmlContext(this)); + context.setContextProperty("index", sourceIndex.row()); + context.setContextProperty("modelIndex", sourceIndex); context.setContextProperty("model", modelMap); QQmlExpression expression(m_scriptString, &context); @@ -87,6 +105,11 @@ QVariant ExpressionRole::data(const QModelIndex& sourceIndex, const QQmlSortFilt qWarning() << expression.error(); return true; } + + if (isCached()) { + m_cache[sourceIndex] = result; + } + return result; } return QVariant(); @@ -99,16 +122,13 @@ void ExpressionRole::updateContext(const QQmlSortFilterProxyModel& proxyModel) // 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); + const auto roleNames = proxyModel.roleNames().values(); + for (const QByteArray& roleName : roleNames) { + modelMap[roleName] = QVariant(); + } + m_context->setContextProperty("index", -1); + m_context->setContextProperty("modelIndex", QModelIndex()); m_context->setContextProperty("model", modelMap); updateExpression(); } @@ -120,7 +140,10 @@ void ExpressionRole::updateExpression() delete m_expression; m_expression = new QQmlExpression(m_scriptString, m_context, 0, this); - connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionRole::invalidate); + connect(m_expression, &QQmlExpression::valueChanged, this, [=] { + invalidateCache(); + invalidate(); + }); m_expression->setNotifyOnValueChanged(true); m_expression->evaluate(); } diff --git a/proxyroles/expressionrole.h b/proxyroles/expressionrole.h index eda3d51..21ab36e 100644 --- a/proxyroles/expressionrole.h +++ b/proxyroles/expressionrole.h @@ -2,6 +2,8 @@ #define EXPRESSIONROLE_H #include "singlerole.h" +#include +#include #include class QQmlExpression; @@ -12,17 +14,24 @@ class ExpressionRole : public SingleRole { Q_OBJECT Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged) + Q_PROPERTY(bool cached READ isCached WRITE setCached NOTIFY cachedChanged) public: using SingleRole::SingleRole; const QQmlScriptString& expression() const; void setExpression(const QQmlScriptString& scriptString); + bool isCached() const; void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override; +public slots: + void setCached(bool cached); + void invalidateCache(); + Q_SIGNALS: void expressionChanged(); + void cachedChanged(bool cached); private: QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override; @@ -32,6 +41,9 @@ class ExpressionRole : public SingleRole QQmlScriptString m_scriptString; QQmlExpression* m_expression = nullptr; QQmlContext* m_context = nullptr; + + QMap m_cache; + bool m_cached = false; }; } From c2cc5ce641ba1c282ee653e999df6cdb21a9d9bf Mon Sep 17 00:00:00 2001 From: Arno Rehn Date: Thu, 11 Mar 2021 23:09:51 +0100 Subject: [PATCH 07/17] Use QHash instead of QMap for faster lookups --- proxyroles/expressionrole.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxyroles/expressionrole.h b/proxyroles/expressionrole.h index 21ab36e..6b21e17 100644 --- a/proxyroles/expressionrole.h +++ b/proxyroles/expressionrole.h @@ -42,7 +42,7 @@ public slots: QQmlExpression* m_expression = nullptr; QQmlContext* m_context = nullptr; - QMap m_cache; + QHash m_cache; bool m_cached = false; }; From 5b05eec8b49fb3ca676e95be843f7107063f07b8 Mon Sep 17 00:00:00 2001 From: Arno Rehn Date: Thu, 11 Mar 2021 23:12:16 +0100 Subject: [PATCH 08/17] Fix clazy warnings --- qqmlsortfilterproxymodel.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/qqmlsortfilterproxymodel.cpp b/qqmlsortfilterproxymodel.cpp index bd06435..bb5026e 100644 --- a/qqmlsortfilterproxymodel.cpp +++ b/qqmlsortfilterproxymodel.cpp @@ -208,11 +208,11 @@ void QQmlSortFilterProxyModel::componentComplete() { m_completed = true; - for (const auto& filter : m_filters) + for (const auto& filter : qAsConst(m_filters)) filter->proxyModelCompleted(*this); - for (const auto& sorter : m_sorters) + for (const auto& sorter : qAsConst(m_sorters)) sorter->proxyModelCompleted(*this); - for (const auto& proxyRole : m_proxyRoles) + for (const auto& proxyRole : qAsConst(m_proxyRoles)) proxyRole->proxyModelCompleted(*this); invalidate(); @@ -266,7 +266,7 @@ QVariantMap QQmlSortFilterProxyModel::get(int row) const QVariantMap map; QModelIndex modelIndex = index(row, 0); QHash roles = roleNames(); - for (QHash::const_iterator it = roles.begin(); it != roles.end(); ++it) + for (QHash::const_iterator it = roles.cbegin(); it != roles.cend(); ++it) map.insert(it.value(), data(modelIndex, it.key())); return map; } @@ -339,7 +339,7 @@ bool QQmlSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelInde bool valueAccepted = !m_filterValue.isValid() || ( m_filterValue == sourceModel()->data(sourceIndex, filterRole()) ); bool baseAcceptsRow = valueAccepted && QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); baseAcceptsRow = baseAcceptsRow && std::all_of(m_filters.begin(), m_filters.end(), - [=, &source_parent] (Filter* filter) { + [=] (Filter* filter) { return filter->filterAcceptsRow(sourceIndex, *this); } ); @@ -436,8 +436,9 @@ void QQmlSortFilterProxyModel::updateRoleNames() auto roles = m_roleNames.keys(); auto maxIt = std::max_element(roles.cbegin(), roles.cend()); int maxRole = maxIt != roles.cend() ? *maxIt : -1; - for (auto proxyRole : m_proxyRoles) { - for (auto roleName : proxyRole->names()) { + for (auto proxyRole : qAsConst(m_proxyRoles)) { + const auto proxyRoleNames = proxyRole->names(); + for (const auto &roleName : proxyRoleNames) { ++maxRole; m_roleNames[maxRole] = roleName.toUtf8(); m_proxyRoleMap[maxRole] = {proxyRole, roleName}; @@ -509,7 +510,7 @@ QVariantMap QQmlSortFilterProxyModel::modelDataMap(const QModelIndex& modelIndex { QVariantMap map; QHash roles = roleNames(); - for (QHash::const_iterator it = roles.begin(); it != roles.end(); ++it) + for (QHash::const_iterator it = roles.cbegin(); it != roles.cend(); ++it) map.insert(it.value(), sourceModel()->data(modelIndex, it.key())); return map; } From 9d92bda60bf9ae3b37c9f61fc1c5abfeb93579d9 Mon Sep 17 00:00:00 2001 From: Arno Rehn Date: Mon, 5 Sep 2022 15:42:33 +0200 Subject: [PATCH 09/17] Support both Qt5 and Qt6 --- CMakeLists.txt | 14 +++++++++----- filters/filtercontainer.cpp | 4 ++-- filters/filtercontainer.h | 10 ++++++++-- proxyroles/proxyrolecontainer.cpp | 4 ++-- proxyroles/proxyrolecontainer.h | 10 ++++++++-- qqmlsortfilterproxymodel.h | 2 +- sorters/sortercontainer.cpp | 4 ++-- sorters/sortercontainer.h | 10 ++++++++-- 8 files changed, 40 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8095071..c0515cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,11 @@ cmake_minimum_required(VERSION 3.14) set(CMAKE_CXX_STANDARD 17) -find_package(Qt6 REQUIRED COMPONENTS +if (NOT QT_VERSION_MAJOR) + set(QT_VERSION_MAJOR 5) +endif() + +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Qml ) @@ -46,8 +50,8 @@ add_library(SortFilterProxyModel OBJECT target_link_libraries(SortFilterProxyModel PRIVATE - Qt6::Core - Qt6::Qml + Qt::Core + Qt::Qml ) if ((MSVC) AND (MSVC_VERSION GREATER_EQUAL 1914)) @@ -56,6 +60,6 @@ endif() target_include_directories(SortFilterProxyModel PUBLIC ${CMAKE_CURRENT_LIST_DIR} - $ - $ + $ + $ ) diff --git a/filters/filtercontainer.cpp b/filters/filtercontainer.cpp index ad955e1..b048280 100644 --- a/filters/filtercontainer.cpp +++ b/filters/filtercontainer.cpp @@ -56,13 +56,13 @@ void FilterContainer::append_filter(QQmlListProperty* list, Filter* filt that->appendFilter(filter); } -qsizetype FilterContainer::count_filter(QQmlListProperty* list) +qqsfpm::FilterContainer::sizetype FilterContainer::count_filter(QQmlListProperty* list) { QList* filters = static_cast*>(list->data); return filters->count(); } -Filter* FilterContainer::at_filter(QQmlListProperty* list, qsizetype index) +Filter* FilterContainer::at_filter(QQmlListProperty* list, qqsfpm::FilterContainer::sizetype index) { QList* filters = static_cast*>(list->data); return filters->at(index); diff --git a/filters/filtercontainer.h b/filters/filtercontainer.h index 9adf41f..853c2fa 100644 --- a/filters/filtercontainer.h +++ b/filters/filtercontainer.h @@ -30,9 +30,15 @@ class FilterContainer { virtual void onFilterRemoved(Filter* filter) = 0; virtual void onFiltersCleared() = 0; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + using sizetype = int; +#else + using sizetype = qsizetype; +#endif + static void append_filter(QQmlListProperty* list, Filter* filter); - static qsizetype count_filter(QQmlListProperty* list); - static Filter* at_filter(QQmlListProperty* list, qsizetype index); + static sizetype count_filter(QQmlListProperty* list); + static Filter* at_filter(QQmlListProperty* list, sizetype index); static void clear_filters(QQmlListProperty* list); }; diff --git a/proxyroles/proxyrolecontainer.cpp b/proxyroles/proxyrolecontainer.cpp index 8418fec..adb7e39 100644 --- a/proxyroles/proxyrolecontainer.cpp +++ b/proxyroles/proxyrolecontainer.cpp @@ -43,13 +43,13 @@ void ProxyRoleContainer::append_proxyRole(QQmlListProperty* list, Pro that->appendProxyRole(proxyRole); } -qsizetype ProxyRoleContainer::count_proxyRole(QQmlListProperty* list) +qqsfpm::ProxyRoleContainer::sizetype ProxyRoleContainer::count_proxyRole(QQmlListProperty* list) { QList* ProxyRoles = static_cast*>(list->data); return ProxyRoles->count(); } -ProxyRole* ProxyRoleContainer::at_proxyRole(QQmlListProperty* list, qsizetype index) +ProxyRole* ProxyRoleContainer::at_proxyRole(QQmlListProperty* list, qqsfpm::ProxyRoleContainer::sizetype index) { QList* ProxyRoles = static_cast*>(list->data); return ProxyRoles->at(index); diff --git a/proxyroles/proxyrolecontainer.h b/proxyroles/proxyrolecontainer.h index 415b8c0..1e60cb9 100644 --- a/proxyroles/proxyrolecontainer.h +++ b/proxyroles/proxyrolecontainer.h @@ -28,9 +28,15 @@ class ProxyRoleContainer { virtual void onProxyRoleRemoved(ProxyRole* proxyRole) = 0; virtual void onProxyRolesCleared() = 0; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + using sizetype = int; +#else + using sizetype = qsizetype; +#endif + static void append_proxyRole(QQmlListProperty* list, ProxyRole* proxyRole); - static qsizetype count_proxyRole(QQmlListProperty* list); - static ProxyRole* at_proxyRole(QQmlListProperty* list, qsizetype index); + static sizetype count_proxyRole(QQmlListProperty* list); + static ProxyRole* at_proxyRole(QQmlListProperty* list, sizetype index); static void clear_proxyRoles(QQmlListProperty* list); }; diff --git a/qqmlsortfilterproxymodel.h b/qqmlsortfilterproxymodel.h index bf4f32e..32fe9a8 100644 --- a/qqmlsortfilterproxymodel.h +++ b/qqmlsortfilterproxymodel.h @@ -95,7 +95,7 @@ class QQmlSortFilterProxyModel : public QSortFilterProxyModel, bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; protected Q_SLOTS: - void resetInternalData() override; + void resetInternalData(); private Q_SLOTS: void queueInvalidateFilter(); diff --git a/sorters/sortercontainer.cpp b/sorters/sortercontainer.cpp index 5255e29..52b45c7 100644 --- a/sorters/sortercontainer.cpp +++ b/sorters/sortercontainer.cpp @@ -56,13 +56,13 @@ void SorterContainer::append_sorter(QQmlListProperty* list, Sorter* sort that->appendSorter(sorter); } -qsizetype SorterContainer::count_sorter(QQmlListProperty* list) +SorterContainer::sizetype SorterContainer::count_sorter(QQmlListProperty* list) { QList* sorters = static_cast*>(list->data); return sorters->count(); } -Sorter* SorterContainer::at_sorter(QQmlListProperty* list, qsizetype index) +Sorter* SorterContainer::at_sorter(QQmlListProperty* list, qqsfpm::SorterContainer::sizetype index) { QList* sorters = static_cast*>(list->data); return sorters->at(index); diff --git a/sorters/sortercontainer.h b/sorters/sortercontainer.h index c60a067..01c35e9 100644 --- a/sorters/sortercontainer.h +++ b/sorters/sortercontainer.h @@ -30,9 +30,15 @@ class SorterContainer { virtual void onSorterRemoved(Sorter* sorter) = 0; virtual void onSortersCleared() = 0; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + using sizetype = int; +#else + using sizetype = qsizetype; +#endif + static void append_sorter(QQmlListProperty* list, Sorter* sorter); - static qsizetype count_sorter(QQmlListProperty* list); - static Sorter* at_sorter(QQmlListProperty* list, qsizetype index); + static sizetype count_sorter(QQmlListProperty* list); + static Sorter* at_sorter(QQmlListProperty* list, sizetype index); static void clear_sorters(QQmlListProperty* list); }; From 1cd6231196de01180f16bf0ec33b0594efb86b09 Mon Sep 17 00:00:00 2001 From: Arno Rehn Date: Sat, 5 Nov 2022 21:40:52 +0100 Subject: [PATCH 10/17] ValueFilter: Restore QVariant comparison behavior from Qt5 In Qt5, QVariant has attempted to convert differing types in order to evaluate equality. This has been useful when integrating with QML, so restore that behavior. --- filters/valuefilter.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/filters/valuefilter.cpp b/filters/valuefilter.cpp index 09e9434..f12ca24 100644 --- a/filters/valuefilter.cpp +++ b/filters/valuefilter.cpp @@ -51,7 +51,18 @@ void ValueFilter::setValue(const QVariant& value) bool ValueFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const { - return !m_value.isValid() || m_value == sourceData(sourceIndex, proxyModel); + QVariant srcData = sourceData(sourceIndex, proxyModel); +#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) + // Implicitly convert the types. This was the behavior in Qt5 and makes QML + // interop much easier, e.g. when comparing QByteArray against QString + if (srcData.metaType() != m_value.metaType()) { + QVariant converted = srcData; + if (converted.convert(m_value.metaType())) { + srcData = converted; + } + } +#endif + return !m_value.isValid() || m_value == srcData; } } From 783b803a56506dc4501b6bcf03f30b26fc4c1448 Mon Sep 17 00:00:00 2001 From: Olaf Mandel Date: Mon, 9 Jan 2023 17:52:59 +0100 Subject: [PATCH 11/17] Fix Qt6 deprecation warnings about QVariant::type() The method `QVariant::Type QVariant::type() const` has been deprecated since Qt 6.0. The recommended replacement is `typeId()` or `metaType()`, but these are not available under Qt5. So choose to use `userType()` instead, which is available under both and should cover the use cases as well. --- qvariantlessthan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qvariantlessthan.cpp b/qvariantlessthan.cpp index d93b6fe..9045f92 100644 --- a/qvariantlessthan.cpp +++ b/qvariantlessthan.cpp @@ -37,8 +37,8 @@ bool lessThan(const QVariant &lhs, const QVariant &rhs) QMetaType::QDateTime, }; - const auto lt = static_cast(lhs.type()); - const auto rt = static_cast(rhs.type()); + const auto lt = static_cast(lhs.userType()); + const auto rt = static_cast(rhs.userType()); if (numericTypes.contains(lt) && numericTypes.contains(rt)) { if (lt == QMetaType::Double || lt == QMetaType::Float || rt == QMetaType::Double || rt == QMetaType::Float) { From 41271ab594bac6e8770f6d8e90ec595048fd62ec Mon Sep 17 00:00:00 2001 From: "R. van Elst" Date: Tue, 18 Jul 2023 11:16:22 +0200 Subject: [PATCH 12/17] Create github actions file for Qt5 --- .github/workflows/cmake-qt5.yml | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/cmake-qt5.yml diff --git a/.github/workflows/cmake-qt5.yml b/.github/workflows/cmake-qt5.yml new file mode 100644 index 0000000..ec7c331 --- /dev/null +++ b/.github/workflows/cmake-qt5.yml @@ -0,0 +1,44 @@ +name: CMake-Qt5 + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install Qt + uses: jurplel/install-qt-action@v3.2.1 + with: + cache: 'true' + cache-key-prefix: 'install-qt-action' + version: '5.15.2' + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + From 31bea0aef50a5477e6ab12642ea1347e1a0bcaec Mon Sep 17 00:00:00 2001 From: "R. van Elst" Date: Tue, 18 Jul 2023 11:15:48 +0200 Subject: [PATCH 13/17] Revert "Merge pull request #4 from MenloSystems/ExpressionRole-caching" This reverts commit 5176c1931d2afe3a9233fe8927467d49cb1414b3, reversing changes made to 33faea08723c1d622c692d70055603b585e478a2. --- proxyroles/expressionrole.cpp | 67 ++++++++++++----------------------- proxyroles/expressionrole.h | 12 ------- 2 files changed, 22 insertions(+), 57 deletions(-) diff --git a/proxyroles/expressionrole.cpp b/proxyroles/expressionrole.cpp index 6a589d3..70af4fa 100644 --- a/proxyroles/expressionrole.cpp +++ b/proxyroles/expressionrole.cpp @@ -62,40 +62,22 @@ void ExpressionRole::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyMo updateContext(proxyModel); } -bool ExpressionRole::isCached() const -{ - return m_cached; -} - -void ExpressionRole::setCached(bool cached) -{ - if (m_cached == cached) { - return; - } - - m_cached = cached; - emit cachedChanged(m_cached); -} - -void ExpressionRole::invalidateCache() -{ - m_cache.clear(); -} - QVariant ExpressionRole::data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) { - if (isCached()) { - const auto it = m_cache.constFind(sourceIndex); - if (it != m_cache.constEnd()) { - return *it; - } - } - if (!m_scriptString.isEmpty()) { - const QVariant modelMap = proxyModel.sourceData(sourceIndex); + QVariantMap modelMap; + QHash roles = proxyModel.roleNames(); + QQmlContext context(qmlContext(this)); - context.setContextProperty("index", sourceIndex.row()); - context.setContextProperty("modelIndex", sourceIndex); + 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); @@ -105,11 +87,6 @@ QVariant ExpressionRole::data(const QModelIndex& sourceIndex, const QQmlSortFilt qWarning() << expression.error(); return true; } - - if (isCached()) { - m_cache[sourceIndex] = result; - } - return result; } return QVariant(); @@ -122,13 +99,16 @@ void ExpressionRole::updateContext(const QQmlSortFilterProxyModel& proxyModel) // what about roles changes ? QVariantMap modelMap; - const auto roleNames = proxyModel.roleNames().values(); - for (const QByteArray& roleName : roleNames) { - modelMap[roleName] = QVariant(); - } + 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("index", -1); - m_context->setContextProperty("modelIndex", QModelIndex()); m_context->setContextProperty("model", modelMap); updateExpression(); } @@ -140,10 +120,7 @@ void ExpressionRole::updateExpression() delete m_expression; m_expression = new QQmlExpression(m_scriptString, m_context, 0, this); - connect(m_expression, &QQmlExpression::valueChanged, this, [=] { - invalidateCache(); - invalidate(); - }); + connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionRole::invalidate); m_expression->setNotifyOnValueChanged(true); m_expression->evaluate(); } diff --git a/proxyroles/expressionrole.h b/proxyroles/expressionrole.h index 6b21e17..eda3d51 100644 --- a/proxyroles/expressionrole.h +++ b/proxyroles/expressionrole.h @@ -2,8 +2,6 @@ #define EXPRESSIONROLE_H #include "singlerole.h" -#include -#include #include class QQmlExpression; @@ -14,24 +12,17 @@ class ExpressionRole : public SingleRole { Q_OBJECT Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged) - Q_PROPERTY(bool cached READ isCached WRITE setCached NOTIFY cachedChanged) public: using SingleRole::SingleRole; const QQmlScriptString& expression() const; void setExpression(const QQmlScriptString& scriptString); - bool isCached() const; void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override; -public slots: - void setCached(bool cached); - void invalidateCache(); - Q_SIGNALS: void expressionChanged(); - void cachedChanged(bool cached); private: QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override; @@ -41,9 +32,6 @@ public slots: QQmlScriptString m_scriptString; QQmlExpression* m_expression = nullptr; QQmlContext* m_context = nullptr; - - QHash m_cache; - bool m_cached = false; }; } From 82820b8a4eb75108658b91e9e2953c92adaefa83 Mon Sep 17 00:00:00 2001 From: Remy van Elst Date: Tue, 18 Jul 2023 11:41:51 +0200 Subject: [PATCH 14/17] Fix after revert --- sorters/rolesorter.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/sorters/rolesorter.cpp b/sorters/rolesorter.cpp index 6699906..c143793 100644 --- a/sorters/rolesorter.cpp +++ b/sorters/rolesorter.cpp @@ -74,3 +74,4 @@ int RoleSorter::compare(const QModelIndex &sourceLeft, const QModelIndex& source } +} \ No newline at end of file From 34162af6ae8c6a59d4212dcbc2c936ff829e61d7 Mon Sep 17 00:00:00 2001 From: "R. van Elst" Date: Tue, 18 Jul 2023 12:11:25 +0200 Subject: [PATCH 15/17] Fix C++ 17 compile warning --- SortFilterProxyModel.pri | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SortFilterProxyModel.pri b/SortFilterProxyModel.pri index 6558571..48a8bf1 100644 --- a/SortFilterProxyModel.pri +++ b/SortFilterProxyModel.pri @@ -1,4 +1,4 @@ -!contains( CONFIG, c\+\+1[14] ): warning("SortFilterProxyModel needs at least c++11, add CONFIG += c++11 to your .pro") +!contains( CONFIG, c\+\+1[147] ): warning("SortFilterProxyModel needs at least c++11, add CONFIG += c++11 to your .pro") INCLUDEPATH += $$PWD From 909278fedb39fc19ff92d8988d0903898bbcd03a Mon Sep 17 00:00:00 2001 From: "R. van Elst" Date: Tue, 18 Jul 2023 12:11:44 +0200 Subject: [PATCH 16/17] Make QVariant compare work with Qt 5.15 and 6.5 --- utils/utils.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/utils/utils.cpp b/utils/utils.cpp index e32f509..d04dc39 100644 --- a/utils/utils.cpp +++ b/utils/utils.cpp @@ -1,38 +1,39 @@ #include "utils.h" - +#include #include namespace qqsfpm { int compareVariants(const QVariant &lhs, const QVariant &rhs) { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // qt 5 // Do the QString check first because otherwise the canConvert check will get hit for strings. - if (lhs.typeId() == QMetaType::QString && rhs.typeId() == QMetaType::QString) { + if (static_cast(lhs.type()) == QMetaType::QString && static_cast(rhs.type()) == QMetaType::QString) { const auto lhsValue = lhs.toString(); const auto rhsValue = rhs.toString(); if (lhsValue == rhsValue) return 0; return lhsValue.compare(rhsValue, Qt::CaseInsensitive); - } else if (lhs.typeId() == QMetaType::Bool && rhs.typeId() == QMetaType::Bool) { + } else if (static_cast(lhs.type()) == QMetaType::Bool && static_cast(rhs.type()) == QMetaType::Bool) { const auto lhsValue = lhs.toBool(); const auto rhsValue = rhs.toBool(); if (lhsValue == rhsValue) return 0; // false < true. return !lhsValue ? -1 : 1; - } else if (lhs.typeId() == QMetaType::QDate && rhs.typeId() == QMetaType::QDate) { + } else if (static_cast(lhs.type()) == QMetaType::QDate && static_cast(rhs.type()) == QMetaType::QDate) { const auto lhsValue = lhs.toDate(); const auto rhsValue = rhs.toDate(); if (lhsValue == rhsValue) return 0; return lhsValue < rhsValue ? -1 : 1; - } else if (lhs.typeId() == QMetaType::QDateTime && rhs.typeId() == QMetaType::QDateTime) { + } else if (static_cast(lhs.type()) == QMetaType::QDateTime && static_cast(rhs.type()) == QMetaType::QDateTime) { const auto lhsValue = lhs.toDateTime(); const auto rhsValue = rhs.toDateTime(); if (lhsValue == rhsValue) return 0; return lhsValue < rhsValue ? -1 : 1; - } else if (lhs.typeId() == QMetaType::QStringList && rhs.typeId() == QMetaType::QStringList) { + } else if (static_cast(lhs.type()) == QMetaType::QStringList && static_cast(rhs.type()) == QMetaType::QStringList) { const auto lhsValue = lhs.toStringList(); const auto rhsValue = rhs.toStringList(); if (lhsValue == rhsValue) @@ -54,6 +55,9 @@ int compareVariants(const QVariant &lhs, const QVariant &rhs) qWarning() << "Don't know how to compare" << lhs << "against" << rhs << "- returning 0"; return 0; +#else + return QPartialOrdering::Less == QVariant::compare(lhs, rhs); +#endif } } From 3f390de7c0383775f4b03d644efe75a3f9f3561e Mon Sep 17 00:00:00 2001 From: "R. van Elst" Date: Tue, 18 Jul 2023 12:14:30 +0200 Subject: [PATCH 17/17] Update readme, add Qt6 actions build --- .github/workflows/cmake-qt6.yml | 44 +++++++++++++++++++++++++++++++++ README.md | 3 +++ 2 files changed, 47 insertions(+) create mode 100644 .github/workflows/cmake-qt6.yml diff --git a/.github/workflows/cmake-qt6.yml b/.github/workflows/cmake-qt6.yml new file mode 100644 index 0000000..260eeb3 --- /dev/null +++ b/.github/workflows/cmake-qt6.yml @@ -0,0 +1,44 @@ +name: CMake-Qt6 + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install Qt + uses: jurplel/install-qt-action@v3.2.1 + with: + cache: 'true' + cache-key-prefix: 'install-qt-action6' + version: '6.5.0' + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DQT_VERSION_MAJOR=6 + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + diff --git a/README.md b/README.md index 2a0cf7c..5c12a68 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ SortFilterProxyModel SortFilterProxyModel is an implementation of `QSortFilterProxyModel` conveniently exposed for QML. +This is a fork that combines the Qt6 port of [MenloSystems/SortFilterProxyModel](https://github.com/MenloSystems/SortFilterProxyModel) with a few of my own fixes to compile and run under both Qt 5.15 and 6.5. +A github actions workflow compiles the code to make sure it keeps compiling on both versions. + Install ------- ##### With [qpm](https://qpm.io) :