File indexing completed on 2024-05-12 15:42:59

0001 /*
0002     SPDX-FileCopyrightText: 2009 Stephen Kelly <steveire@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "krecursivefilterproxymodel.h"
0008 
0009 #if KITEMMODELS_BUILD_DEPRECATED_SINCE(5, 65)
0010 #include <QMetaMethod>
0011 
0012 // Maintainability note:
0013 // This class invokes some Q_PRIVATE_SLOTs in QSortFilterProxyModel which are
0014 // private API and could be renamed or removed at any time.
0015 // If they are renamed, the invocations can be updated with an #if (QT_VERSION(...))
0016 // If they are removed, then layout{AboutToBe}Changed Q_SIGNALS should be used when the source model
0017 // gets new rows or has rowsremoved or moved. The Q_PRIVATE_SLOT invocation is an optimization
0018 // because layout{AboutToBe}Changed is expensive and causes the entire mapping of the tree in QSFPM
0019 // to be cleared, even if only a part of it is dirty.
0020 // Stephen Kelly, 30 April 2010.
0021 
0022 // All this is temporary anyway, the long term solution is support in QSFPM: https://codereview.qt-project.org/151000
0023 
0024 class KRecursiveFilterProxyModelPrivate
0025 {
0026     Q_DECLARE_PUBLIC(KRecursiveFilterProxyModel)
0027     KRecursiveFilterProxyModel *q_ptr;
0028 
0029 public:
0030     KRecursiveFilterProxyModelPrivate(KRecursiveFilterProxyModel *model)
0031         : q_ptr(model)
0032         , completeInsert(false)
0033     {
0034         qRegisterMetaType<QModelIndex>("QModelIndex");
0035     }
0036 
0037     inline QMetaMethod findMethod(const char *signature) const
0038     {
0039         Q_Q(const KRecursiveFilterProxyModel);
0040         const int idx = q->metaObject()->indexOfMethod(signature);
0041         Q_ASSERT(idx != -1);
0042         return q->metaObject()->method(idx);
0043     }
0044 
0045     // Convenience methods for invoking the QSFPM Q_SLOTS. Those slots must be invoked with invokeMethod
0046     // because they are Q_PRIVATE_SLOTs
0047     inline void invokeDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>())
0048     {
0049         Q_Q(KRecursiveFilterProxyModel);
0050         // required for Qt 5.5 and upwards, see commit f96baeb75fc in qtbase
0051         static const QMetaMethod m = findMethod("_q_sourceDataChanged(QModelIndex,QModelIndex,QVector<int>)");
0052         bool success = m.invoke(q, Qt::DirectConnection, Q_ARG(QModelIndex, topLeft), Q_ARG(QModelIndex, bottomRight), Q_ARG(QVector<int>, roles));
0053         Q_UNUSED(success);
0054         Q_ASSERT(success);
0055     }
0056 
0057     inline void invokeRowsInserted(const QModelIndex &source_parent, int start, int end)
0058     {
0059         Q_Q(KRecursiveFilterProxyModel);
0060         static const QMetaMethod m = findMethod("_q_sourceRowsInserted(QModelIndex,int,int)");
0061         bool success = m.invoke(q, Qt::DirectConnection, Q_ARG(QModelIndex, source_parent), Q_ARG(int, start), Q_ARG(int, end));
0062         Q_UNUSED(success);
0063         Q_ASSERT(success);
0064     }
0065 
0066     inline void invokeRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
0067     {
0068         Q_Q(KRecursiveFilterProxyModel);
0069         static const QMetaMethod m = findMethod("_q_sourceRowsAboutToBeInserted(QModelIndex,int,int)");
0070         bool success = m.invoke(q, Qt::DirectConnection, Q_ARG(QModelIndex, source_parent), Q_ARG(int, start), Q_ARG(int, end));
0071         Q_UNUSED(success);
0072         Q_ASSERT(success);
0073     }
0074 
0075     inline void invokeRowsRemoved(const QModelIndex &source_parent, int start, int end)
0076     {
0077         Q_Q(KRecursiveFilterProxyModel);
0078         static const QMetaMethod m = findMethod("_q_sourceRowsRemoved(QModelIndex,int,int)");
0079         bool success = m.invoke(q, Qt::DirectConnection, Q_ARG(QModelIndex, source_parent), Q_ARG(int, start), Q_ARG(int, end));
0080         Q_UNUSED(success);
0081         Q_ASSERT(success);
0082     }
0083 
0084     inline void invokeRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
0085     {
0086         Q_Q(KRecursiveFilterProxyModel);
0087         static const QMetaMethod m = findMethod("_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int)");
0088         bool success = m.invoke(q, Qt::DirectConnection, Q_ARG(QModelIndex, source_parent), Q_ARG(int, start), Q_ARG(int, end));
0089         Q_UNUSED(success);
0090         Q_ASSERT(success);
0091     }
0092 
0093     void sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right, const QVector<int> &roles = QVector<int>());
0094     void sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end);
0095     void sourceRowsInserted(const QModelIndex &source_parent, int start, int end);
0096     void sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end);
0097     void sourceRowsRemoved(const QModelIndex &source_parent, int start, int end);
0098 
0099     /**
0100     Force QSortFilterProxyModel to re-evaluate whether to hide or show index and its parents.
0101     */
0102     void refreshAscendantMapping(const QModelIndex &index);
0103 
0104     QModelIndex lastFilteredOutAscendant(const QModelIndex &index);
0105 
0106     bool completeInsert;
0107     QModelIndex lastHiddenAscendantForInsert;
0108 };
0109 
0110 void KRecursiveFilterProxyModelPrivate::sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right, const QVector<int> &roles)
0111 {
0112     QModelIndex source_parent = source_top_left.parent();
0113     Q_ASSERT(source_bottom_right.parent() == source_parent); // don't know how to handle different parents in this code...
0114 
0115     // Tell the world.
0116     invokeDataChanged(source_top_left, source_bottom_right, roles);
0117 
0118     // We can't find out if the change really matters to us or not, for a lack of a dataAboutToBeChanged signal (or a cache).
0119     // TODO: add a set of roles that we care for, so we can at least ignore the rest.
0120 
0121     // Even if we knew the visibility was just toggled, we also can't find out what
0122     // was the last filtered out ascendant (on show, like sourceRowsAboutToBeInserted does)
0123     // or the last to-be-filtered-out ascendant (on hide, like sourceRowsRemoved does)
0124     // So we have to refresh all parents.
0125     QModelIndex sourceParent = source_parent;
0126     while (sourceParent.isValid()) {
0127         invokeDataChanged(sourceParent, sourceParent, roles);
0128         sourceParent = sourceParent.parent();
0129     }
0130 }
0131 
0132 QModelIndex KRecursiveFilterProxyModelPrivate::lastFilteredOutAscendant(const QModelIndex &idx)
0133 {
0134     Q_Q(KRecursiveFilterProxyModel);
0135     QModelIndex last = idx;
0136     QModelIndex index = idx.parent();
0137     while (index.isValid() && !q->filterAcceptsRow(index.row(), index.parent())) {
0138         last = index;
0139         index = index.parent();
0140     }
0141     return last;
0142 }
0143 
0144 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
0145 {
0146     Q_Q(KRecursiveFilterProxyModel);
0147 
0148     if (!source_parent.isValid() || q->filterAcceptsRow(source_parent.row(), source_parent.parent())) {
0149         // If the parent is already in the model (directly or indirectly), we can just pass on the signal.
0150         invokeRowsAboutToBeInserted(source_parent, start, end);
0151         completeInsert = true;
0152     } else {
0153         // OK, so parent is not in the model.
0154         // Maybe the grand parent neither.. Go up until the first one that is.
0155         lastHiddenAscendantForInsert = lastFilteredOutAscendant(source_parent);
0156     }
0157 }
0158 
0159 void KRecursiveFilterProxyModelPrivate::sourceRowsInserted(const QModelIndex &source_parent, int start, int end)
0160 {
0161     Q_Q(KRecursiveFilterProxyModel);
0162 
0163     if (completeInsert) {
0164         // If the parent is already in the model, we can just pass on the signal.
0165         completeInsert = false;
0166         invokeRowsInserted(source_parent, start, end);
0167         return;
0168     }
0169 
0170     bool requireRow = false;
0171     for (int row = start; row <= end; ++row) {
0172         if (q->filterAcceptsRow(row, source_parent)) {
0173             requireRow = true;
0174             break;
0175         }
0176     }
0177 
0178     if (!requireRow) {
0179         // The new rows doesn't have any descendants that match the filter. Filter them out.
0180         return;
0181     }
0182 
0183     // Make QSFPM realize that lastHiddenAscendantForInsert should be shown now
0184     invokeDataChanged(lastHiddenAscendantForInsert, lastHiddenAscendantForInsert);
0185 }
0186 
0187 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
0188 {
0189     invokeRowsAboutToBeRemoved(source_parent, start, end);
0190 }
0191 
0192 void KRecursiveFilterProxyModelPrivate::sourceRowsRemoved(const QModelIndex &source_parent, int start, int end)
0193 {
0194     Q_Q(KRecursiveFilterProxyModel);
0195 
0196     invokeRowsRemoved(source_parent, start, end);
0197 
0198     // Find out if removing this visible row means that some ascendant
0199     // row can now be hidden.
0200     // We go up until we find a row that should still be visible
0201     // and then make QSFPM re-evaluate the last one we saw before that, to hide it.
0202 
0203     QModelIndex toHide;
0204     QModelIndex sourceAscendant = source_parent;
0205     while (sourceAscendant.isValid()) {
0206         if (q->filterAcceptsRow(sourceAscendant.row(), sourceAscendant.parent())) {
0207             break;
0208         }
0209         toHide = sourceAscendant;
0210         sourceAscendant = sourceAscendant.parent();
0211     }
0212     if (toHide.isValid()) {
0213         invokeDataChanged(toHide, toHide);
0214     }
0215 }
0216 
0217 KRecursiveFilterProxyModel::KRecursiveFilterProxyModel(QObject *parent)
0218     : QSortFilterProxyModel(parent)
0219     , d_ptr(new KRecursiveFilterProxyModelPrivate(this))
0220 {
0221     setDynamicSortFilter(true);
0222 }
0223 
0224 KRecursiveFilterProxyModel::~KRecursiveFilterProxyModel() = default;
0225 
0226 bool KRecursiveFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
0227 {
0228     // TODO: Implement some caching so that if one match is found on the first pass, we can return early results
0229     // when the subtrees are checked by QSFPM.
0230     if (acceptRow(sourceRow, sourceParent)) {
0231         return true;
0232     }
0233 
0234     QModelIndex source_index = sourceModel()->index(sourceRow, 0, sourceParent);
0235     Q_ASSERT(source_index.isValid());
0236     bool accepted = false;
0237 
0238     const int numChildren = sourceModel()->rowCount(source_index);
0239     for (int row = 0, rows = numChildren; row < rows; ++row) {
0240         if (filterAcceptsRow(row, source_index)) {
0241             accepted = true;
0242             break;
0243         }
0244     }
0245 
0246     return accepted;
0247 }
0248 
0249 QModelIndexList KRecursiveFilterProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
0250 {
0251     if (role < Qt::UserRole) {
0252         return QSortFilterProxyModel::match(start, role, value, hits, flags);
0253     }
0254 
0255     QModelIndexList list;
0256     if (!sourceModel()) {
0257         return list;
0258     }
0259 
0260     QModelIndex proxyIndex;
0261     const auto lst = sourceModel()->match(mapToSource(start), role, value, hits, flags);
0262     for (const QModelIndex &idx : lst) {
0263         proxyIndex = mapFromSource(idx);
0264         if (proxyIndex.isValid()) {
0265             list << proxyIndex;
0266         }
0267     }
0268 
0269     return list;
0270 }
0271 
0272 bool KRecursiveFilterProxyModel::acceptRow(int sourceRow, const QModelIndex &sourceParent) const
0273 {
0274     return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
0275 }
0276 
0277 void KRecursiveFilterProxyModel::setSourceModel(QAbstractItemModel *model)
0278 {
0279     // Standard disconnect of the previous source model, if present
0280     if (sourceModel()) {
0281         disconnect(sourceModel(),
0282                    SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector<int>)),
0283                    this,
0284                    SLOT(sourceDataChanged(QModelIndex, QModelIndex, QVector<int>)));
0285 
0286         disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(sourceRowsAboutToBeInserted(QModelIndex, int, int)));
0287 
0288         disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(sourceRowsInserted(QModelIndex, int, int)));
0289 
0290         disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex, int, int)));
0291 
0292         disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(sourceRowsRemoved(QModelIndex, int, int)));
0293     }
0294 
0295     QSortFilterProxyModel::setSourceModel(model);
0296 
0297     // Disconnect in the QSortFilterProxyModel. These methods will be invoked manually
0298     // in invokeDataChanged, invokeRowsInserted etc.
0299     //
0300     // The reason for that is that when the source model adds new rows for example, the new rows
0301     // May not match the filter, but maybe their child items do match.
0302     //
0303     // Source model before insert:
0304     //
0305     // - A
0306     // - B
0307     // - - C
0308     // - - D
0309     // - - - E
0310     // - - - F
0311     // - - - G
0312     // - H
0313     // - I
0314     //
0315     // If the A F and L (which doesn't exist in the source model yet) match the filter
0316     // the proxy will be:
0317     //
0318     // - A
0319     // - B
0320     // - - D
0321     // - - - F
0322     //
0323     // New rows are inserted in the source model below H:
0324     //
0325     // - A
0326     // - B
0327     // - - C
0328     // - - D
0329     // - - - E
0330     // - - - F
0331     // - - - G
0332     // - H
0333     // - - J
0334     // - - K
0335     // - - - L
0336     // - I
0337     //
0338     // As L matches the filter, it should be part of the KRecursiveFilterProxyModel.
0339     //
0340     // - A
0341     // - B
0342     // - - D
0343     // - - - F
0344     // - H
0345     // - - K
0346     // - - - L
0347     //
0348     // when the QSortFilterProxyModel gets a notification about new rows in H, it only checks
0349     // J and K to see if they match, ignoring L, and therefore not adding it to the proxy.
0350     // To work around that, we make sure that the QSFPM slot which handles that change in
0351     // the source model (_q_sourceRowsAboutToBeInserted) does not get called directly.
0352     // Instead we connect the sourceModel signal to our own slot in *this (sourceRowsAboutToBeInserted)
0353     // Inside that method, the entire new subtree is queried (J, K *and* L) to see if there is a match,
0354     // then the relevant Q_SLOTS in QSFPM are invoked.
0355     // In the example above, we need to tell the QSFPM that H should be queried again to see if
0356     // it matches the filter. It did not before, because L did not exist before. Now it does. That is
0357     // achieved by telling the QSFPM that the data changed for H, which causes it to requery this class
0358     // to see if H matches the filter (which it now does as L now exists).
0359     // That is done in sourceRowsInserted.
0360 
0361     if (!model) {
0362         return;
0363     }
0364 
0365     disconnect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector<int>)), this, SLOT(_q_sourceDataChanged(QModelIndex, QModelIndex, QVector<int>)));
0366 
0367     disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex, int, int)));
0368 
0369     disconnect(model, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(_q_sourceRowsInserted(QModelIndex, int, int)));
0370 
0371     disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex, int, int)));
0372 
0373     disconnect(model, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(_q_sourceRowsRemoved(QModelIndex, int, int)));
0374 
0375     // Slots for manual invoking of QSortFilterProxyModel methods.
0376     connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector<int>)), this, SLOT(sourceDataChanged(QModelIndex, QModelIndex, QVector<int>)));
0377 
0378     connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(sourceRowsAboutToBeInserted(QModelIndex, int, int)));
0379 
0380     connect(model, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(sourceRowsInserted(QModelIndex, int, int)));
0381 
0382     connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex, int, int)));
0383 
0384     connect(model, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(sourceRowsRemoved(QModelIndex, int, int)));
0385 }
0386 
0387 #include "moc_krecursivefilterproxymodel.cpp"
0388 #endif