Warning, file /frameworks/kitemmodels/src/core/krecursivefilterproxymodel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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