diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2d76cdc..a87c8f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(${TARGET} PRIVATE newiconitem.cpp managedtexturenode.cpp wheelhandler.cpp + qqmlsortfilterproxymodel.cpp desktop/menupopupwindow.cpp diff --git a/src/fishui.cpp b/src/fishui.cpp index 2e2a289..1990070 100644 --- a/src/fishui.cpp +++ b/src/fishui.cpp @@ -25,6 +25,7 @@ #include "windowhelper.h" #include "newiconitem.h" #include "wheelhandler.h" +#include "qqmlsortfilterproxymodel.h" #include "desktop/menupopupwindow.h" @@ -59,6 +60,7 @@ void FishUI::registerTypes(const char *uri) qmlRegisterType(uri, 1, 0, "IconItem"); qmlRegisterType(uri, 1, 0, "MenuPopupWindow"); qmlRegisterType(uri, 1, 0, "WheelHandler"); + qmlRegisterType(uri, 1, 0, "SortFilterProxyModel"); qmlRegisterSingletonType(componentUrl(QStringLiteral("Theme.qml")), uri, 1, 0, "Theme"); qmlRegisterSingletonType(componentUrl(QStringLiteral("Units.qml")), uri, 1, 0, "Units"); diff --git a/src/qqmlsortfilterproxymodel.cpp b/src/qqmlsortfilterproxymodel.cpp new file mode 100644 index 0000000..cb66aee --- /dev/null +++ b/src/qqmlsortfilterproxymodel.cpp @@ -0,0 +1,290 @@ +/**************************************************************************** + * + * Copyright (C) 2018 Pierre-Yves Siret + * + * $BEGIN_LICENSE:MIT$ + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * $END_LICENSE$ + ***************************************************************************/ + +#include "qqmlsortfilterproxymodel.h" +#include + +QQmlSortFilterProxyModel::QQmlSortFilterProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) + , m_filterExpression(0) + , m_compareExpression(0) +{ + connect(this, &QAbstractProxyModel::sourceModelChanged, this, + &QQmlSortFilterProxyModel::updateRoles); + connect(this, &QAbstractItemModel::modelReset, this, &QQmlSortFilterProxyModel::updateRoles); + connect(this, &QAbstractItemModel::rowsInserted, this, &QQmlSortFilterProxyModel::countChanged); + connect(this, &QAbstractItemModel::rowsRemoved, this, &QQmlSortFilterProxyModel::countChanged); + connect(this, &QAbstractItemModel::modelReset, this, &QQmlSortFilterProxyModel::countChanged); + connect(this, &QAbstractItemModel::layoutChanged, this, + &QQmlSortFilterProxyModel::countChanged); + setDynamicSortFilter(true); +} + +int QQmlSortFilterProxyModel::count() const +{ + return rowCount(); +} + +QHash QQmlSortFilterProxyModel::roleNames() const +{ + return sourceModel() ? sourceModel()->roleNames() : QHash(); +} + +const QString &QQmlSortFilterProxyModel::filterRoleName() const +{ + return m_filterRoleName; +} + +void QQmlSortFilterProxyModel::setFilterRoleName(const QString &filterRoleName) +{ + if (m_filterRoleName == filterRoleName) + return; + + m_filterRoleName = filterRoleName; + updateFilterRole(); + emit filterRoleNameChanged(); +} + +QString QQmlSortFilterProxyModel::filterPattern() const +{ + return filterRegExp().pattern(); +} + +void QQmlSortFilterProxyModel::setFilterPattern(const QString &filterPattern) +{ + QRegExp regExp = filterRegExp(); + if (regExp.pattern() == filterPattern) + return; + + regExp.setPattern(filterPattern); + QSortFilterProxyModel::setFilterRegExp(regExp); + 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); + emit filterPatternSyntaxChanged(); +} + +const QVariant &QQmlSortFilterProxyModel::filterValue() const +{ + return m_filterValue; +} + +void QQmlSortFilterProxyModel::setFilterValue(const QVariant &filterValue) +{ + if (m_filterValue == filterValue) + return; + + m_filterValue = filterValue; + invalidateFilter(); + emit filterValueChanged(); +} + +const QQmlScriptString &QQmlSortFilterProxyModel::filterExpression() const +{ + return m_filterScriptString; +} + +void QQmlSortFilterProxyModel::setFilterExpression(const QQmlScriptString &filterScriptString) +{ + if (m_filterScriptString == filterScriptString) + return; + + m_filterScriptString = filterScriptString; + QQmlContext *context = new QQmlContext(qmlContext(this)); + + auto roles = roleNames().values(); + QVariantMap map; + for (const QByteArray &roleName : roles) + map.insert(QString::fromLatin1(roleName), QVariant()); + + context->setContextProperty(QStringLiteral("model"), map); + context->setContextProperty(QStringLiteral("index"), -1); + + delete (m_filterExpression); + m_filterExpression = new QQmlExpression(m_filterScriptString, context, 0, this); + connect(m_filterExpression, &QQmlExpression::valueChanged, this, + &QQmlSortFilterProxyModel::invalidateFilter); + m_filterExpression->setNotifyOnValueChanged(true); + m_filterExpression->evaluate(); + + emit filterExpressionChanged(); +} + +const QString &QQmlSortFilterProxyModel::sortRoleName() const +{ + return m_sortRoleName; +} + +void QQmlSortFilterProxyModel::setSortRoleName(const QString &sortRoleName) +{ + if (m_sortRoleName == sortRoleName) + return; + + m_sortRoleName = sortRoleName; + updateSortRole(); + emit sortRoleNameChanged(); +} + +void QQmlSortFilterProxyModel::setSortOrder(Qt::SortOrder sortOrder) +{ + if (!m_sortRoleName.isEmpty()) + sort(0, sortOrder); + emit sortOrderChanged(); +} + +const QQmlScriptString &QQmlSortFilterProxyModel::sortExpression() const +{ + return m_compareScriptString; +} + +void QQmlSortFilterProxyModel::setSortExpression(const QQmlScriptString &compareScriptString) +{ + if (m_compareScriptString == compareScriptString) + return; + + m_compareScriptString = compareScriptString; + QQmlContext *context = new QQmlContext(qmlContext(this)); + + auto roles = roleNames().values(); + QVariantMap map; + for (const QByteArray &roleName : roles) + map.insert(QString::fromLatin1(roleName), QVariant()); + + context->setContextProperty(QStringLiteral("modelLeft"), map); + context->setContextProperty(QStringLiteral("indexLeft"), -1); + context->setContextProperty(QStringLiteral("modelRight"), map); + context->setContextProperty(QStringLiteral("indexRight"), -1); + + delete (m_compareExpression); + m_compareExpression = new QQmlExpression(m_compareScriptString, context, 0, this); + connect(m_compareExpression, &QQmlExpression::valueChanged, this, + &QQmlSortFilterProxyModel::invalidate); + m_compareExpression->setNotifyOnValueChanged(true); + m_compareExpression->evaluate(); + + emit sortExpressionChanged(); +} + +bool QQmlSortFilterProxyModel::filterAcceptsRow(int source_row, + const QModelIndex &source_parent) const +{ + QModelIndex modelIndex = sourceModel()->index(source_row, 0, source_parent); + bool valueAccepted = !m_filterValue.isValid() + || (m_filterValue == sourceModel()->data(modelIndex, filterRole())); + bool baseAcceptsRow = + valueAccepted && QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); + if (baseAcceptsRow && !m_filterScriptString.isEmpty()) { + QVariantMap map = modelDataMap(modelIndex); + + QQmlContext context(qmlContext(this)); + context.setContextProperty(QStringLiteral("model"), map); + context.setContextProperty(QStringLiteral("index"), source_row); + QQmlExpression expression(m_filterScriptString, &context, 0); + QVariant result = expression.evaluate(); + + if (!expression.hasError()) + return result.toBool(); + else + qWarning() << expression.error(); + } + return baseAcceptsRow; +} + +bool QQmlSortFilterProxyModel::lessThan(const QModelIndex &source_left, + const QModelIndex &source_right) const +{ + if (!m_compareScriptString.isEmpty()) { + QQmlContext context(qmlContext(this)); + context.setContextProperty(QStringLiteral("modelLeft"), modelDataMap(source_left)); + context.setContextProperty(QStringLiteral("indexLeft"), source_left.row()); + context.setContextProperty(QStringLiteral("modelRight"), modelDataMap(source_right)); + context.setContextProperty(QStringLiteral("indexRight"), source_right.row()); + + QQmlExpression expression(m_compareScriptString, &context, 0); + QVariant result = expression.evaluate(); + + if (!expression.hasError()) + return result.toBool(); + else + qWarning() << expression.error(); + } + return QSortFilterProxyModel::lessThan(source_left, source_right); +} + +void QQmlSortFilterProxyModel::invalidateFilter() +{ + QSortFilterProxyModel::invalidateFilter(); +} + +void QQmlSortFilterProxyModel::updateFilterRole() +{ + QList filterRoles = roleNames().keys(m_filterRoleName.toUtf8()); + if (!filterRoles.empty()) { + setFilterRole(filterRoles.first()); + } +} + +void QQmlSortFilterProxyModel::updateSortRole() +{ + QList sortRoles = roleNames().keys(m_sortRoleName.toUtf8()); + if (!sortRoles.empty()) { + setSortRole(sortRoles.first()); + sort(0, sortOrder()); + } +} + +void QQmlSortFilterProxyModel::updateRoles() +{ + updateFilterRole(); + updateSortRole(); +} + +QVariantMap QQmlSortFilterProxyModel::modelDataMap(const QModelIndex &modelIndex) const +{ + QVariantMap map; + QHash roles = roleNames(); + for (QHash::const_iterator it = roles.constBegin(); it != roles.constEnd(); ++it) + map.insert(QString::fromLatin1(it.value()), sourceModel()->data(modelIndex, it.key())); + return map; +} + +#include "moc_qqmlsortfilterproxymodel.cpp" diff --git a/src/qqmlsortfilterproxymodel.h b/src/qqmlsortfilterproxymodel.h new file mode 100644 index 0000000..20b6651 --- /dev/null +++ b/src/qqmlsortfilterproxymodel.h @@ -0,0 +1,128 @@ +/**************************************************************************** + * Copyright (C) 2018 Pierre-Yves Siret + * + * $BEGIN_LICENSE:MIT$ + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * $END_LICENSE$ + ***************************************************************************/ + +#ifndef QQMLSORTFILTERPROXYMODEL_H +#define QQMLSORTFILTERPROXYMODEL_H + +#include +#include + +class QQmlSortFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + 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(QQmlScriptString filterExpression READ filterExpression WRITE setFilterExpression + NOTIFY filterExpressionChanged) + + Q_PROPERTY( + QString sortRoleName READ sortRoleName WRITE setSortRoleName NOTIFY sortRoleNameChanged) + Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged) + Q_PROPERTY(QQmlScriptString sortExpression READ sortExpression WRITE setSortExpression NOTIFY sortExpressionChanged) + +public: + enum PatternSyntax { + RegExp = QRegExp::RegExp, + Wildcard = QRegExp::Wildcard, + FixedString = QRegExp::FixedString, + RegExp2 = QRegExp::RegExp2, + WildcardUnix = QRegExp::WildcardUnix, + W3CXmlSchema11 = QRegExp::W3CXmlSchema11 + }; + Q_ENUM(PatternSyntax) + + QQmlSortFilterProxyModel(QObject *parent = 0); + + int count() const; + + QHash roleNames() const override; + + const QString &filterRoleName() const; + void setFilterRoleName(const QString &filterRoleName); + + QString filterPattern() const; + void setFilterPattern(const QString &filterPattern); + + PatternSyntax filterPatternSyntax() const; + void setFilterPatternSyntax(PatternSyntax patternSyntax); + + const QVariant &filterValue() const; + void setFilterValue(const QVariant &filterValue); + + const QQmlScriptString &filterExpression() const; + void setFilterExpression(const QQmlScriptString &filterScriptString); + + const QString &sortRoleName() const; + void setSortRoleName(const QString &sortRoleName); + + void setSortOrder(Qt::SortOrder sortOrder); + + const QQmlScriptString &sortExpression() const; + void setSortExpression(const QQmlScriptString &compareScriptString); + +signals: + void countChanged(); + + void filterRoleNameChanged(); + void filterPatternSyntaxChanged(); + void filterPatternChanged(); + void filterValueChanged(); + void filterExpressionChanged(); + + void sortRoleNameChanged(); + void sortOrderChanged(); + void sortExpressionChanged(); + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; + +private slots: + void invalidateFilter(); + void updateFilterRole(); + void updateSortRole(); + void updateRoles(); + +private: + QVariantMap modelDataMap(const QModelIndex &modelIndex) const; + + QString m_filterRoleName; + QString m_sortRoleName; + + QQmlScriptString m_filterScriptString; + QQmlExpression *m_filterExpression; + + QQmlScriptString m_compareScriptString; + QQmlExpression *m_compareExpression; + QVariant m_filterValue; +}; + +#endif // QQMLSORTFILTERPROXYMODEL_H