KDEUI
krecursivefilterproxymodel.cpp
Go to the documentation of this file.
00001 /* 00002 Copyright (c) 2009 Stephen Kelly <steveire@gmail.com> 00003 00004 This library is free software; you can redistribute it and/or modify it 00005 under the terms of the GNU Library General Public License as published by 00006 the Free Software Foundation; either version 2 of the License, or (at your 00007 option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, but WITHOUT 00010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00012 License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to the 00016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00017 02110-1301, USA. 00018 */ 00019 00020 #include "krecursivefilterproxymodel.h" 00021 00022 #include <kdebug.h> 00023 00024 // Maintainability note: 00025 // This class invokes some Q_PRIVATE_SLOTs in QSortFilterProxyModel which are 00026 // private API and could be renamed or removed at any time. 00027 // If they are renamed, the invokations can be updated with an #if (QT_VERSION(...)) 00028 // If they are removed, then layout{AboutToBe}Changed signals should be used when the source model 00029 // gets new rows or has rowsremoved or moved. The Q_PRIVATE_SLOT invokation is an optimization 00030 // because layout{AboutToBe}Changed is expensive and causes the entire mapping of the tree in QSFPM 00031 // to be cleared, even if only a part of it is dirty. 00032 // Stephen Kelly, 30 April 2010. 00033 00034 class KRecursiveFilterProxyModelPrivate 00035 { 00036 Q_DECLARE_PUBLIC(KRecursiveFilterProxyModel) 00037 KRecursiveFilterProxyModel *q_ptr; 00038 public: 00039 KRecursiveFilterProxyModelPrivate(KRecursiveFilterProxyModel *model) 00040 : q_ptr(model), 00041 ignoreRemove(false), 00042 completeInsert(false), 00043 completeRemove(false) 00044 { 00045 qRegisterMetaType<QModelIndex>( "QModelIndex" ); 00046 } 00047 00048 // Convenience methods for invoking the QSFPM slots. Those slots must be invoked with invokeMethod 00049 // because they are Q_PRIVATE_SLOTs 00050 inline void invokeDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) 00051 { 00052 Q_Q(KRecursiveFilterProxyModel); 00053 bool success = QMetaObject::invokeMethod(q, "_q_sourceDataChanged", Qt::DirectConnection, 00054 Q_ARG(QModelIndex, topLeft), 00055 Q_ARG(QModelIndex, bottomRight)); 00056 Q_UNUSED(success); 00057 Q_ASSERT(success); 00058 } 00059 00060 inline void invokeRowsInserted(const QModelIndex &source_parent, int start, int end) 00061 { 00062 Q_Q(KRecursiveFilterProxyModel); 00063 bool success = QMetaObject::invokeMethod(q, "_q_sourceRowsInserted", Qt::DirectConnection, 00064 Q_ARG(QModelIndex, source_parent), 00065 Q_ARG(int, start), 00066 Q_ARG(int, end)); 00067 Q_UNUSED(success); 00068 Q_ASSERT(success); 00069 } 00070 00071 inline void invokeRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end) 00072 { 00073 Q_Q(KRecursiveFilterProxyModel); 00074 bool success = QMetaObject::invokeMethod(q, "_q_sourceRowsAboutToBeInserted", Qt::DirectConnection, 00075 Q_ARG(QModelIndex, source_parent), 00076 Q_ARG(int, start), 00077 Q_ARG(int, end)); 00078 Q_UNUSED(success); 00079 Q_ASSERT(success); 00080 } 00081 00082 inline void invokeRowsRemoved(const QModelIndex &source_parent, int start, int end) 00083 { 00084 Q_Q(KRecursiveFilterProxyModel); 00085 bool success = QMetaObject::invokeMethod(q, "_q_sourceRowsRemoved", Qt::DirectConnection, 00086 Q_ARG(QModelIndex, source_parent), 00087 Q_ARG(int, start), 00088 Q_ARG(int, end)); 00089 Q_UNUSED(success); 00090 Q_ASSERT(success); 00091 } 00092 00093 inline void invokeRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end) 00094 { 00095 Q_Q(KRecursiveFilterProxyModel); 00096 bool success = QMetaObject::invokeMethod(q, "_q_sourceRowsAboutToBeRemoved", Qt::DirectConnection, 00097 Q_ARG(QModelIndex, source_parent), 00098 Q_ARG(int, start), 00099 Q_ARG(int, end)); 00100 Q_UNUSED(success); 00101 Q_ASSERT(success); 00102 } 00103 00104 void sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right); 00105 void sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end); 00106 void sourceRowsInserted(const QModelIndex &source_parent, int start, int end); 00107 void sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end); 00108 void sourceRowsRemoved(const QModelIndex &source_parent, int start, int end); 00109 00116 void refreshAscendantMapping(const QModelIndex &index, bool refreshAll = false); 00117 00118 bool ignoreRemove; 00119 bool completeInsert; 00120 bool completeRemove; 00121 }; 00122 00123 void KRecursiveFilterProxyModelPrivate::sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right) 00124 { 00125 Q_Q(KRecursiveFilterProxyModel); 00126 00127 QModelIndex source_parent = source_top_left.parent(); 00128 00129 if (!source_parent.isValid() || q->filterAcceptsRow(source_parent.row(), source_parent.parent())) 00130 { 00131 invokeDataChanged(source_top_left, source_bottom_right); 00132 return; 00133 } 00134 00135 bool requireRow = false; 00136 for (int row = source_top_left.row(); row <= source_bottom_right.row(); ++row) 00137 if (q->filterAcceptsRow(row, source_parent)) 00138 { 00139 requireRow = true; 00140 break; 00141 } 00142 00143 if (!requireRow) // None of the changed rows are now required in the model. 00144 return; 00145 00146 refreshAscendantMapping(source_parent); 00147 } 00148 00149 void KRecursiveFilterProxyModelPrivate::refreshAscendantMapping(const QModelIndex &index, bool refreshAll) 00150 { 00151 Q_Q(KRecursiveFilterProxyModel); 00152 00153 Q_ASSERT(index.isValid()); 00154 QModelIndex lastAscendant = index; 00155 QModelIndex sourceAscendant = index.parent(); 00156 // We got a matching descendant, so find the right place to insert the row. 00157 // We need to tell the QSortFilterProxyModel that the first child between an existing row in the model 00158 // has changed data so that it will get a mapping. 00159 while(sourceAscendant.isValid() && !q->acceptRow(sourceAscendant.row(), sourceAscendant.parent())) 00160 { 00161 if (refreshAll) 00162 invokeDataChanged(sourceAscendant, sourceAscendant); 00163 00164 lastAscendant = sourceAscendant; 00165 sourceAscendant = sourceAscendant.parent(); 00166 } 00167 00168 // Inform the model that its data changed so that it creates new mappings and finds the rows which now match the filter. 00169 invokeDataChanged(lastAscendant, lastAscendant); 00170 } 00171 00172 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end) 00173 { 00174 Q_Q(KRecursiveFilterProxyModel); 00175 00176 if (!source_parent.isValid() || q->filterAcceptsRow(source_parent.row(), source_parent.parent())) 00177 { 00178 invokeRowsAboutToBeInserted(source_parent, start, end); 00179 completeInsert = true; 00180 } 00181 } 00182 00183 void KRecursiveFilterProxyModelPrivate::sourceRowsInserted(const QModelIndex &source_parent, int start, int end) 00184 { 00185 Q_Q(KRecursiveFilterProxyModel); 00186 00187 if (completeInsert) 00188 { 00189 completeInsert = false; 00190 invokeRowsInserted(source_parent, start, end); 00191 // If the parent is already in the model, we can just pass on the signal. 00192 return; 00193 } 00194 00195 bool requireRow = false; 00196 for (int row = start; row <= end; ++row) 00197 { 00198 if (q->filterAcceptsRow(row, source_parent)) 00199 { 00200 requireRow = true; 00201 break; 00202 } 00203 } 00204 00205 if (!requireRow) 00206 { 00207 // The row doesn't have descendants that match the filter. Filter it out. 00208 return; 00209 } 00210 00211 refreshAscendantMapping(source_parent); 00212 } 00213 00214 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end) 00215 { 00216 Q_Q(KRecursiveFilterProxyModel); 00217 00218 if (source_parent.isValid() && q->filterAcceptsRow(source_parent.row(), source_parent.parent())) 00219 { 00220 invokeRowsAboutToBeRemoved(source_parent, start, end); 00221 completeRemove = true; 00222 return; 00223 } 00224 00225 bool accepted = false; 00226 for (int row = start; row <= end; ++row) 00227 { 00228 if (q->filterAcceptsRow(row, source_parent)) 00229 { 00230 accepted = true; 00231 break; 00232 } 00233 } 00234 if (!accepted) 00235 { 00236 // All removed rows are already filtered out. We don't care about the signal. 00237 ignoreRemove = true; 00238 return; 00239 } 00240 completeRemove = true; 00241 invokeRowsAboutToBeRemoved(source_parent, start, end); 00242 } 00243 00244 void KRecursiveFilterProxyModelPrivate::sourceRowsRemoved(const QModelIndex &source_parent, int start, int end) 00245 { 00246 if (completeRemove) 00247 { 00248 completeRemove = false; 00249 // Source parent is already in the model. 00250 invokeRowsRemoved(source_parent, start, end); 00251 // fall through. After removing rows, we need to refresh things so that intermediates will be removed too if necessary. 00252 } 00253 00254 if (ignoreRemove) 00255 { 00256 ignoreRemove = false; 00257 return; 00258 } 00259 00260 // Refresh intermediate rows too. 00261 // This is needed because QSFPM only invalidates the mapping for the 00262 // index range given to dataChanged, not its children. 00263 if (source_parent.isValid()) 00264 refreshAscendantMapping(source_parent, true); 00265 } 00266 00267 KRecursiveFilterProxyModel::KRecursiveFilterProxyModel(QObject* parent) 00268 : QSortFilterProxyModel(parent), d_ptr(new KRecursiveFilterProxyModelPrivate(this)) 00269 { 00270 setDynamicSortFilter(true); 00271 } 00272 00273 KRecursiveFilterProxyModel::~KRecursiveFilterProxyModel() 00274 { 00275 delete d_ptr; 00276 } 00277 00278 bool KRecursiveFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const 00279 { 00280 // TODO: Implement some caching so that if one match is found on the first pass, we can return early results 00281 // when the subtrees are checked by QSFPM. 00282 if (acceptRow(sourceRow, sourceParent)) 00283 return true; 00284 00285 QModelIndex source_index = sourceModel()->index(sourceRow, 0, sourceParent); 00286 Q_ASSERT(source_index.isValid()); 00287 bool accepted = false; 00288 00289 for (int row = 0 ; row < sourceModel()->rowCount(source_index); ++row) 00290 if (filterAcceptsRow(row, source_index)) 00291 accepted = true; // Need to do this in a loop so that all siblings in a parent get processed, not just the first. 00292 00293 return accepted; 00294 } 00295 00296 QModelIndexList KRecursiveFilterProxyModel::match( const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags ) const 00297 { 00298 if ( role < Qt::UserRole ) 00299 return QSortFilterProxyModel::match( start, role, value, hits, flags ); 00300 00301 QModelIndexList list; 00302 QModelIndex proxyIndex; 00303 foreach ( const QModelIndex &idx, sourceModel()->match( mapToSource( start ), role, value, hits, flags ) ) { 00304 proxyIndex = mapFromSource( idx ); 00305 if ( proxyIndex.isValid() ) 00306 list << proxyIndex; 00307 } 00308 00309 return list; 00310 } 00311 00312 bool KRecursiveFilterProxyModel::acceptRow(int sourceRow, const QModelIndex& sourceParent) const 00313 { 00314 return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); 00315 } 00316 00317 void KRecursiveFilterProxyModel::setSourceModel(QAbstractItemModel* model) 00318 { 00319 // Standard disconnect. 00320 disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), 00321 this, SLOT(sourceDataChanged(QModelIndex,QModelIndex))); 00322 00323 disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), 00324 this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int))); 00325 00326 disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), 00327 this, SLOT(sourceRowsInserted(QModelIndex,int,int))); 00328 00329 disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), 00330 this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int))); 00331 00332 disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), 00333 this, SLOT(sourceRowsRemoved(QModelIndex,int,int))); 00334 00335 QSortFilterProxyModel::setSourceModel(model); 00336 00337 // Disconnect in the QSortFilterProxyModel. These methods will be invoked manually 00338 // in invokeDataChanged, invokeRowsInserted etc. 00339 // 00340 // The reason for that is that when the source model adds new rows for example, the new rows 00341 // May not match the filter, but maybe their child items do match. 00342 // 00343 // Source model before insert: 00344 // 00345 // - A 00346 // - B 00347 // - - C 00348 // - - D 00349 // - - - E 00350 // - - - F 00351 // - - - G 00352 // - H 00353 // - I 00354 // 00355 // If the A F and L (which doesn't exist in the source model yet) match the filter 00356 // the proxy will be: 00357 // 00358 // - A 00359 // - B 00360 // - - D 00361 // - - - F 00362 // 00363 // New rows are inserted in the source model below H: 00364 // 00365 // - A 00366 // - B 00367 // - - C 00368 // - - D 00369 // - - - E 00370 // - - - F 00371 // - - - G 00372 // - H 00373 // - - J 00374 // - - K 00375 // - - - L 00376 // - I 00377 // 00378 // As L matches the filter, it should be part of the KRecursiveFilterProxyModel. 00379 // 00380 // - A 00381 // - B 00382 // - - D 00383 // - - - F 00384 // - H 00385 // - - K 00386 // - - - L 00387 // 00388 // when the QSortFilterProxyModel gets a notification about new rows in H, it only checks 00389 // J and K to see if they match, ignoring L, and therefore not adding it to the proxy. 00390 // To work around that, we make sure that the QSFPM slot which handles that change in 00391 // the source model (_q_sourceRowsAboutToBeInserted) does not get called directly. 00392 // Instead we connect the sourceModel signal to our own slot in *this (sourceRowsAboutToBeInserted) 00393 // Inside that method, the entire new subtree is queried (J, K *and* L) to see if there is a match, 00394 // then the relevant slots in QSFPM are invoked. 00395 // In the example above, we need to tell the QSFPM that H should be queried again to see if 00396 // it matches the filter. It did not before, because L did not exist before. Now it does. That is 00397 // achieved by telling the QSFPM that the data changed for H, which causes it to requery this class 00398 // to see if H matches the filter (which it now does as L now exists). 00399 // That is done in refreshAscendantMapping. 00400 00401 disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), 00402 this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex))); 00403 00404 disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), 00405 this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int))); 00406 00407 disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), 00408 this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int))); 00409 00410 disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), 00411 this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int))); 00412 00413 disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), 00414 this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int))); 00415 00416 // Slots for manual invoking of QSortFilterProxyModel methods. 00417 connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), 00418 this, SLOT(sourceDataChanged(QModelIndex,QModelIndex))); 00419 00420 connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), 00421 this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int))); 00422 00423 connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), 00424 this, SLOT(sourceRowsInserted(QModelIndex,int,int))); 00425 00426 connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), 00427 this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int))); 00428 00429 connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), 00430 this, SLOT(sourceRowsRemoved(QModelIndex,int,int))); 00431 00432 } 00433 00434 #include "krecursivefilterproxymodel.moc"
KDE 4.6 API Reference