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"