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}} + 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/CMakeLists.txt b/CMakeLists.txt index 3d3bf2b..3fa5944 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,22 @@ -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 +if (NOT QT_VERSION_MAJOR) + set(QT_VERSION_MAJOR 5) +endif() + +find_package(Qt${QT_VERSION_MAJOR} 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 add_library(SortFilterProxyModel OBJECT qqmlsortfilterproxymodel.cpp + qvariantlessthan.cpp filters/filter.cpp filters/filtercontainer.cpp filters/rolefilter.cpp @@ -40,10 +45,22 @@ add_library(SortFilterProxyModel OBJECT proxyroles/regexprole.cpp sorters/filtersorter.cpp proxyroles/filterrole.cpp - ) + utils/utils.cpp + utils/utils.h +) + +target_link_libraries(SortFilterProxyModel + PRIVATE + Qt::Core + Qt::Qml +) + +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} - $ - $ - ) + $ + $ +) 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) : diff --git a/SortFilterProxyModel.pri b/SortFilterProxyModel.pri index 22f7a83..48a8bf1 100644 --- a/SortFilterProxyModel.pri +++ b/SortFilterProxyModel.pri @@ -1,8 +1,9 @@ -!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 HEADERS += $$PWD/qqmlsortfilterproxymodel.h \ + $$PWD/qvariantlessthan.h \ $$PWD/filters/filter.h \ $$PWD/filters/filtercontainer.h \ $$PWD/filters/rolefilter.h \ @@ -27,9 +28,11 @@ 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/qvariantlessthan.cpp \ $$PWD/filters/filter.cpp \ $$PWD/filters/filtercontainer.cpp \ $$PWD/filters/rolefilter.cpp \ @@ -57,4 +60,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..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); } -int 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, int 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 4fc06f3..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 int count_filter(QQmlListProperty* list); - static Filter* at_filter(QQmlListProperty* list, int index); + static sizetype count_filter(QQmlListProperty* list); + static Filter* at_filter(QQmlListProperty* list, sizetype index); static void clear_filters(QQmlListProperty* list); }; diff --git a/filters/rangefilter.cpp b/filters/rangefilter.cpp index 2a6fde2..41d37f2 100644 --- a/filters/rangefilter.cpp +++ b/filters/rangefilter.cpp @@ -1,5 +1,10 @@ #include "rangefilter.h" +#include "qvariantlessthan.h" + + + + namespace qqsfpm { /*! @@ -128,11 +133,13 @@ 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); + (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/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/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; } } diff --git a/proxyroles/proxyrolecontainer.cpp b/proxyroles/proxyrolecontainer.cpp index f8ea665..adb7e39 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) +qqsfpm::ProxyRoleContainer::sizetype 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, 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 bcd932e..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 int count_proxyRole(QQmlListProperty* list); - static ProxyRole* at_proxyRole(QQmlListProperty* list, int 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.cpp b/qqmlsortfilterproxymodel.cpp index bd06435..3dbef45 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; @@ -208,11 +191,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 +249,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 +322,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 +419,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 +493,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; } diff --git a/qqmlsortfilterproxymodel.h b/qqmlsortfilterproxymodel.h index dbe0229..32fe9a8 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(); diff --git a/qvariantlessthan.cpp b/qvariantlessthan.cpp new file mode 100644 index 0000000..9045f92 --- /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.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) { + 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..c143793 100644 --- a/sorters/rolesorter.cpp +++ b/sorters/rolesorter.cpp @@ -1,6 +1,11 @@ #include "rolesorter.h" #include "qqmlsortfilterproxymodel.h" +#include "qvariantlessthan.h" + + + + namespace qqsfpm { /*! @@ -56,14 +61,17 @@ 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); + + 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; -} } + + +} \ No newline at end of file diff --git a/sorters/sortercontainer.cpp b/sorters/sortercontainer.cpp index f986e37..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); } -int 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, int 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 016cc6d..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 int count_sorter(QQmlListProperty* list); - static Sorter* at_sorter(QQmlListProperty* list, int index); + static sizetype count_sorter(QQmlListProperty* list); + static Sorter* at_sorter(QQmlListProperty* list, sizetype 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..d04dc39 --- /dev/null +++ b/utils/utils.cpp @@ -0,0 +1,63 @@ +#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 (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 (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 (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 (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 (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) + 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; +#else + return QPartialOrdering::Less == QVariant::compare(lhs, rhs); +#endif +} + +} 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