File indexing completed on 2025-01-19 04:52:04

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