• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

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"

KDEUI

Skip menu "KDEUI"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal