diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -31,6 +31,7 @@ if (${Qt5Qml_FOUND}) ecm_add_tests( kconcatenaterows_qml.cpp + ksortfilterproxymodel_qml.cpp LINK_LIBRARIES KF5::ItemModels Qt5::Test Qt5::Qml ) endif() diff --git a/autotests/ksortfilterproxymodel_qml.cpp b/autotests/ksortfilterproxymodel_qml.cpp new file mode 100644 --- /dev/null +++ b/autotests/ksortfilterproxymodel_qml.cpp @@ -0,0 +1,56 @@ +/* + Copyright (C) 2019 David Edmundson + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ + +#include + +#include +#include + +#include + +#include + +class tst_KSortFilterProxyModelQml : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testFilterCallback(); +}; + +void tst_KSortFilterProxyModelQml::testFilterCallback() +{ + QQmlApplicationEngine app; + app.load(QFINDTESTDATA("ksortfilterproxymodeltest.qml")); + + QCOMPARE(app.rootObjects().count(), 1); + + auto concatModel = qobject_cast(app.rootObjects().first()); + QVERIFY(concatModel); + + QCOMPARE(concatModel->rowCount(), 5); + QCOMPARE(concatModel->data(concatModel->index(0, 0)).toString(), "1"); + QCOMPARE(concatModel->data(concatModel->index(1, 0)).toString(), "3"); + QCOMPARE(concatModel->data(concatModel->index(2, 0)).toString(), "5"); + QCOMPARE(concatModel->data(concatModel->index(3, 0)).toString(), "7"); + QCOMPARE(concatModel->data(concatModel->index(4, 0)).toString(), "9"); +} + +QTEST_MAIN(tst_KSortFilterProxyModelQml) + +#include "ksortfilterproxymodel_qml.moc" diff --git a/autotests/ksortfilterproxymodeltest.qml b/autotests/ksortfilterproxymodeltest.qml new file mode 100644 --- /dev/null +++ b/autotests/ksortfilterproxymodeltest.qml @@ -0,0 +1,16 @@ +import QtQuick 2.0 +import org.kde.kitemmodels 1.0 + +// only include odd_numbers + +KSortFilterProxyModel +{ + sourceModel: KNumberModel { + minimumValue: 1 + maximumValue: 10 + } + filterCallback: function(row, value) { + return value % 2 == 1; + }; +} + diff --git a/src/qml/CMakeLists.txt b/src/qml/CMakeLists.txt --- a/src/qml/CMakeLists.txt +++ b/src/qml/CMakeLists.txt @@ -1,8 +1,11 @@ set(corebindings_SRCS plugin.cpp kconcatenaterowsproxymodel_qml.cpp + ksortfilterproxymodel.cpp ) +ecm_qt_declare_logging_category(corebindings_SRCS HEADER kitemmodels_debug.h IDENTIFIER KITEMMODELS_LOG CATEGORY_NAME kf5.kitemmodels) + add_library(itemmodelsplugin SHARED ${corebindings_SRCS}) target_link_libraries(itemmodelsplugin Qt5::Qml diff --git a/src/qml/ksortfilterproxymodel.h b/src/qml/ksortfilterproxymodel.h new file mode 100644 --- /dev/null +++ b/src/qml/ksortfilterproxymodel.h @@ -0,0 +1,142 @@ +/* + * Copyright 2010 by Marco MArtin + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef KSORTFILTERPROXYMODEL_H +#define KSORTFILTERPROXYMODEL_H + +#include +#include +#include +#include + +/** + * @class SortFilterModel + * @short Filter and sort an existing QAbstractItemModel + */ +class KSortFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + /** + * The source model of this sorting proxy model. + */ + Q_PROPERTY(QAbstractItemModel *sourceModel READ sourceModel WRITE setModel NOTIFY sourceModelChanged) + + /** + * The regular expression for the filter, only items with their filterRole matching filterRegExp will be displayed + */ + Q_PROPERTY(QString filterRegExp READ filterRegExp WRITE setFilterRegExp NOTIFY filterRegExpChanged) + + /** + * The string for the filter, only items with their filterRole matching filterString will be displayed + */ + Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged) + + /** + * A JavaScript callable that is passed the source model row index as first argument and the value + * of filterRole as second argument. The callable's return value is evaluated as boolean to determine + * whether the row is accepted (true) or filtered out (false). It overrides the default implementation + * that uses filterRegExp or filterString; while filterCallable is set those two properties are + * ignored. Attempts to write a non-callable to this property are silently ignored, but you can set + * it to null. + */ + Q_PROPERTY(QJSValue filterCallback READ filterCallback WRITE setFilterCallback NOTIFY filterCallbackChanged) + + /** + * The role of the sourceModel on which filterRegExp must be applied. + */ + Q_PROPERTY(QString filterRole READ filterRole WRITE setFilterRole) + + /** + * The role of the sourceModel that will be used for sorting. if empty the order will be left unaltered + */ + Q_PROPERTY(QString sortRole READ sortRole WRITE setSortRole) + + /** + * One of Qt.Ascending or Qt.Descending + */ + Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder) + + /** + * How many items are in this model + */ + Q_PROPERTY(int count READ count NOTIFY countChanged) + +public: + explicit KSortFilterProxyModel(QObject *parent = nullptr); + ~KSortFilterProxyModel(); + + void setModel(QAbstractItemModel *source); + + void setFilterRegExp(const QString &exp); + QString filterRegExp() const; + + void setFilterString(const QString &filterString); + QString filterString() const; + + void setFilterCallback(const QJSValue &callback); + QJSValue filterCallback() const; + + void setFilterRole(const QString &role); + QString filterRole() const; + + void setSortRole(const QString &role); + QString sortRole() const; + + void setSortOrder(const Qt::SortOrder order); + + int count() const + { + return QSortFilterProxyModel::rowCount(); + } + + /** + * Returns the item at index in the list model. + * This allows the item data to be accessed (but not modified) from JavaScript. + * It returns an Object with a property for each role. + * + * @param i the row we want + */ + Q_INVOKABLE QVariantMap get(int i) const; + + Q_INVOKABLE int mapRowToSource(int i) const; + + Q_INVOKABLE int mapRowFromSource(int i) const; + +Q_SIGNALS: + void countChanged(); + void sourceModelChanged(QObject *); + void filterRegExpChanged(const QString &); + void filterStringChanged(const QString &); + void filterCallbackChanged(const QJSValue &); + +protected: + int roleNameToId(const QString &name) const; + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +protected Q_SLOTS: + void syncRoleNames(); + +private: + QString m_filterRole; + QString m_sortRole; + QString m_filterString; + QJSValue m_filterCallback; + QHash m_roleIds; +}; +#endif diff --git a/src/qml/ksortfilterproxymodel.cpp b/src/qml/ksortfilterproxymodel.cpp new file mode 100644 --- /dev/null +++ b/src/qml/ksortfilterproxymodel.cpp @@ -0,0 +1,201 @@ +/* + * Copyright 2010 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "ksortfilterproxymodel.h" + +#include +#include + +#include "kitemmodels_debug.h" + +KSortFilterProxyModel::KSortFilterProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setDynamicSortFilter(true); + connect(this, &QAbstractItemModel::rowsInserted, + this, &KSortFilterProxyModel::countChanged); + connect(this, &QAbstractItemModel::rowsRemoved, + this, &KSortFilterProxyModel::countChanged); + connect(this, &QAbstractItemModel::modelReset, + this, &KSortFilterProxyModel::countChanged); +} + +KSortFilterProxyModel::~KSortFilterProxyModel() +{ +} + +void KSortFilterProxyModel::syncRoleNames() +{ + if (!sourceModel()) { + return; + } + + m_roleIds.clear(); + const QHash rNames = roleNames(); + m_roleIds.reserve(rNames.count()); + for (auto i = rNames.constBegin(); i != rNames.constEnd(); ++i) { + m_roleIds[QString::fromUtf8(i.value())] = i.key(); + } +} + +int KSortFilterProxyModel::roleNameToId(const QString &name) const +{ + return m_roleIds.value(name, Qt::DisplayRole); +} + +void KSortFilterProxyModel::setModel(QAbstractItemModel *model) +{ + if (model == sourceModel()) { + return; + } + + QSortFilterProxyModel::setSourceModel(model); + syncRoleNames(); + setFilterRole(m_filterRole); + setSortRole(m_sortRole); + + emit sourceModelChanged(model); +} + +bool KSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const +{ + if (m_filterCallback.isCallable()) { + QJSValueList args; + args << QJSValue(source_row); + + const QModelIndex idx = sourceModel()->index(source_row, filterKeyColumn(), source_parent); + QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine(); + args << engine->toScriptValue(idx.data(m_roleIds.value(m_filterRole))); + + return const_cast(this)->m_filterCallback.call(args).toBool(); + } + + return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); +} + +void KSortFilterProxyModel::setFilterRegExp(const QString &exp) +{ + if (exp == filterRegExp()) { + return; + } + QSortFilterProxyModel::setFilterRegularExpression(QRegularExpression(exp, QRegularExpression::CaseInsensitiveOption)); + Q_EMIT filterRegExpChanged(exp); +} + +QString KSortFilterProxyModel::filterRegExp() const +{ + return QSortFilterProxyModel::filterRegExp().pattern(); +} + +void KSortFilterProxyModel::setFilterString(const QString &filterString) +{ + if (filterString == m_filterString) { + return; + } + m_filterString = filterString; + QSortFilterProxyModel::setFilterFixedString(filterString); + Q_EMIT filterStringChanged(filterString); +} + +QString KSortFilterProxyModel::filterString() const +{ + return m_filterString; +} + +QJSValue KSortFilterProxyModel::filterCallback() const +{ + return m_filterCallback; +} + +void KSortFilterProxyModel::setFilterCallback(const QJSValue& callback) +{ + if (m_filterCallback.strictlyEquals(callback)) { + return; + } + + if (!callback.isNull() && !callback.isCallable()) { + return; + } + + m_filterCallback = callback; + invalidateFilter(); + + Q_EMIT filterCallbackChanged(callback); +} + +void KSortFilterProxyModel::setFilterRole(const QString &role) +{ + QSortFilterProxyModel::setFilterRole(roleNameToId(role)); + m_filterRole = role; +} + +QString KSortFilterProxyModel::filterRole() const +{ + return m_filterRole; +} + +void KSortFilterProxyModel::setSortRole(const QString &role) +{ + m_sortRole = role; + if (role.isEmpty()) { + sort(-1, Qt::AscendingOrder); + } else if (sourceModel()) { + QSortFilterProxyModel::setSortRole(roleNameToId(role)); + sort(0, sortOrder()); + } +} + +QString KSortFilterProxyModel::sortRole() const +{ + return m_sortRole; +} + +void KSortFilterProxyModel::setSortOrder(const Qt::SortOrder order) +{ + sort(0, order); +} + +QVariantMap KSortFilterProxyModel::get(int row) const +{ + QModelIndex idx = index(row, 0); + QVariantMap hash; + + const QHash rNames = roleNames(); + for (auto i = rNames.begin(); i != rNames.end(); ++i) { + hash[QString::fromUtf8(i.value())] = data(idx, i.key()); + } + + return hash; +} + +int KSortFilterProxyModel::mapRowToSource(int row) const +{ + QModelIndex idx = index(row, 0); + return mapToSource(idx).row(); +} + +int KSortFilterProxyModel::mapRowFromSource(int row) const +{ + if (!sourceModel()) { + qWarning(KITEMMODELS_LOG) << "No source model defined!"; + return -1; + } + QModelIndex idx = sourceModel()->index(row, 0); + return mapFromSource(idx).row(); +} diff --git a/src/qml/plugin.cpp b/src/qml/plugin.cpp --- a/src/qml/plugin.cpp +++ b/src/qml/plugin.cpp @@ -24,6 +24,7 @@ #include #include +#include "ksortfilterproxymodel.h" #include "kconcatenaterowsproxymodel_qml.h" void Plugin::initializeEngine(QQmlEngine *engine, const char *uri) @@ -38,6 +39,7 @@ qmlRegisterExtendedType(uri, 1, 0, "KConcatenateRowsProxyModel"); qmlRegisterType(uri, 1, 0, "KDescendantsProxyModel"); qmlRegisterType(uri, 1, 0, "KNumberModel"); + qmlRegisterType(uri, 1, 0, "KSortFilterProxyModel"); } diff --git a/src/qml/sortfiltermodel.h b/src/qml/sortfiltermodel.h new file mode 100644 --- /dev/null +++ b/src/qml/sortfiltermodel.h @@ -0,0 +1,144 @@ +/* + * Copyright 2010 by Marco MArtin + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SORTFILTERMODEL_H +#define SORTFILTERMODEL_H + +#include +#include +#include +#include + +/** + * @class SortFilterModel + * @short Filter and sort an existing QAbstractItemModel + */ +class SortFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + /** + * The source model of this sorting proxy model. + */ + Q_PROPERTY(QAbstractItemModel *sourceModel READ sourceModel WRITE setModel NOTIFY sourceModelChanged) + + /** + * The regular expression for the filter, only items with their filterRole matching filterRegExp will be displayed + */ + Q_PROPERTY(QString filterRegExp READ filterRegExp WRITE setFilterRegExp NOTIFY filterRegExpChanged) + + /** + * The string for the filter, only items with their filterRole matching filterString will be displayed + */ + Q_PROPERTY(QString filterString READ filterString WRITE setFilterString NOTIFY filterStringChanged REVISION 1) + + /** + * A JavaScript callable that is passed the source model row index as first argument and the value + * of filterRole as second argument. The callable's return value is evaluated as boolean to determine + * whether the row is accepted (true) or filtered out (false). It overrides the default implementation + * that uses filterRegExp or filterString; while filterCallable is set those two properties are + * ignored. Attempts to write a non-callable to this property are silently ignored, but you can set + * it to null. + */ + Q_PROPERTY(QJSValue filterCallback READ filterCallback WRITE setFilterCallback NOTIFY filterCallbackChanged REVISION 1) + + /** + * The role of the sourceModel on which filterRegExp must be applied. + */ + Q_PROPERTY(QString filterRole READ filterRole WRITE setFilterRole) + + /** + * The role of the sourceModel that will be used for sorting. if empty the order will be left unaltered + */ + Q_PROPERTY(QString sortRole READ sortRole WRITE setSortRole) + + /** + * One of Qt.Ascending or Qt.Descending + */ + Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder) + + /** + * How many items are in this model + */ + Q_PROPERTY(int count READ count NOTIFY countChanged) + + friend class DataModel; + +public: + explicit SortFilterModel(QObject *parent = nullptr); + ~SortFilterModel(); + + void setModel(QAbstractItemModel *source); + + void setFilterRegExp(const QString &exp); + QString filterRegExp() const; + + void setFilterString(const QString &filterString); + QString filterString() const; + + void setFilterCallback(const QJSValue &callback); + QJSValue filterCallback() const; + + void setFilterRole(const QString &role); + QString filterRole() const; + + void setSortRole(const QString &role); + QString sortRole() const; + + void setSortOrder(const Qt::SortOrder order); + + int count() const + { + return QSortFilterProxyModel::rowCount(); + } + + /** + * Returns the item at index in the list model. + * This allows the item data to be accessed (but not modified) from JavaScript. + * It returns an Object with a property for each role. + * + * @param i the row we want + */ + Q_INVOKABLE QVariantMap get(int i) const; + + Q_INVOKABLE int mapRowToSource(int i) const; + + Q_INVOKABLE int mapRowFromSource(int i) const; + +Q_SIGNALS: + void countChanged(); + void sourceModelChanged(QObject *); + void filterRegExpChanged(const QString &); + void filterStringChanged(const QString &); + void filterCallbackChanged(const QJSValue &); + +protected: + int roleNameToId(const QString &name) const; + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +protected Q_SLOTS: + void syncRoleNames(); + +private: + QString m_filterRole; + QString m_sortRole; + QString m_filterString; + QJSValue m_filterCallback; + QHash m_roleIds; +}; +#endif diff --git a/src/qml/sortfiltermodel.cpp b/src/qml/sortfiltermodel.cpp new file mode 100644 --- /dev/null +++ b/src/qml/sortfiltermodel.cpp @@ -0,0 +1,201 @@ +/* + * Copyright 2010 by Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "sortfiltermodel.h" + +#include +#include + +#include "kitemmodels_debug.h" + +SortFilterModel::SortFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ + setDynamicSortFilter(true); + connect(this, &QAbstractItemModel::rowsInserted, + this, &SortFilterModel::countChanged); + connect(this, &QAbstractItemModel::rowsRemoved, + this, &SortFilterModel::countChanged); + connect(this, &QAbstractItemModel::modelReset, + this, &SortFilterModel::countChanged); +} + +SortFilterModel::~SortFilterModel() +{ +} + +void SortFilterModel::syncRoleNames() +{ + if (!sourceModel()) { + return; + } + + m_roleIds.clear(); + const QHash rNames = roleNames(); + m_roleIds.reserve(rNames.count()); + for (auto i = rNames.constBegin(); i != rNames.constEnd(); ++i) { + m_roleIds[QString::fromUtf8(i.value())] = i.key(); + } +} + +int SortFilterModel::roleNameToId(const QString &name) const +{ + return m_roleIds.value(name, Qt::DisplayRole); +} + +void SortFilterModel::setModel(QAbstractItemModel *model) +{ + if (model == sourceModel()) { + return; + } + + QSortFilterProxyModel::setSourceModel(model); + syncRoleNames(); + setFilterRole(m_filterRole); + setSortRole(m_sortRole); + + emit sourceModelChanged(model); +} + +bool SortFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const +{ + if (m_filterCallback.isCallable()) { + QJSValueList args; + args << QJSValue(source_row); + + const QModelIndex idx = sourceModel()->index(source_row, filterKeyColumn(), source_parent); + QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine(); + args << engine->toScriptValue(idx.data(m_roleIds.value(m_filterRole))); + + return const_cast(this)->m_filterCallback.call(args).toBool(); + } + + return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); +} + +void SortFilterModel::setFilterRegExp(const QString &exp) +{ + if (exp == filterRegExp()) { + return; + } + QSortFilterProxyModel::setFilterRegExp(QRegExp(exp, Qt::CaseInsensitive)); + Q_EMIT filterRegExpChanged(exp); +} + +QString SortFilterModel::filterRegExp() const +{ + return QSortFilterProxyModel::filterRegExp().pattern(); +} + +void SortFilterModel::setFilterString(const QString &filterString) +{ + if (filterString == m_filterString) { + return; + } + m_filterString = filterString; + QSortFilterProxyModel::setFilterFixedString(filterString); + Q_EMIT filterStringChanged(filterString); +} + +QString SortFilterModel::filterString() const +{ + return m_filterString; +} + +QJSValue SortFilterModel::filterCallback() const +{ + return m_filterCallback; +} + +void SortFilterModel::setFilterCallback(const QJSValue& callback) +{ + if (m_filterCallback.strictlyEquals(callback)) { + return; + } + + if (!callback.isNull() && !callback.isCallable()) { + return; + } + + m_filterCallback = callback; + invalidateFilter(); + + Q_EMIT filterCallbackChanged(callback); +} + +void SortFilterModel::setFilterRole(const QString &role) +{ + QSortFilterProxyModel::setFilterRole(roleNameToId(role)); + m_filterRole = role; +} + +QString SortFilterModel::filterRole() const +{ + return m_filterRole; +} + +void SortFilterModel::setSortRole(const QString &role) +{ + m_sortRole = role; + if (role.isEmpty()) { + sort(-1, Qt::AscendingOrder); + } else if (sourceModel()) { + QSortFilterProxyModel::setSortRole(roleNameToId(role)); + sort(0, sortOrder()); + } +} + +QString SortFilterModel::sortRole() const +{ + return m_sortRole; +} + +void SortFilterModel::setSortOrder(const Qt::SortOrder order) +{ + sort(0, order); +} + +QVariantMap SortFilterModel::get(int row) const +{ + QModelIndex idx = index(row, 0); + QVariantMap hash; + + const QHash rNames = roleNames(); + for (auto i = rNames.begin(); i != rNames.end(); ++i) { + hash[QString::fromUtf8(i.value())] = data(idx, i.key()); + } + + return hash; +} + +int SortFilterModel::mapRowToSource(int row) const +{ + QModelIndex idx = index(row, 0); + return mapToSource(idx).row(); +} + +int SortFilterModel::mapRowFromSource(int row) const +{ + if (!sourceModel()) { + qWarning(KITEMMODELS_LOG) << "No source model defined!"; + return -1; + } + QModelIndex idx = sourceModel()->index(row, 0); + return mapFromSource(idx).row(); +}