• Skip to content
  • Skip to link menu
KDE 4.5 API Reference
  • KDE API Reference
  • KDE-PIM Libraries
  • Sitemap
  • Contact Us
 

akonadi

krecursivefilterproxymodel.cpp

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 invocations 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 invocation 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 bool KRecursiveFilterProxyModel::acceptRow(int sourceRow, const QModelIndex& sourceParent) const
00297 {
00298   return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
00299 }
00300 
00301 void KRecursiveFilterProxyModel::setSourceModel(QAbstractItemModel* model)
00302 {
00303   // Standard disconnect.
00304   disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
00305       this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
00306 
00307   disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
00308       this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
00309 
00310   disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
00311       this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
00312 
00313   disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
00314       this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
00315 
00316   disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
00317       this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
00318 
00319   QSortFilterProxyModel::setSourceModel(model);
00320 
00321   // Disconnect in the QSortFilterProxyModel. These methods will be invoked manually
00322   // in invokeDataChanged, invokeRowsInserted etc.
00323   //
00324   // The reason for that is that when the source model adds new rows for example, the new rows
00325   // May not match the filter, but maybe their child items do match.
00326   //
00327   // Source model before insert:
00328   //
00329   // - A
00330   // - B
00331   // - - C
00332   // - - D
00333   // - - - E
00334   // - - - F
00335   // - - - G
00336   // - H
00337   // - I
00338   //
00339   // If the A F and L (which doesn't exist in the source model yet) match the filter
00340   // the proxy will be:
00341   //
00342   // - A
00343   // - B
00344   // - - D
00345   // - - - F
00346   //
00347   // New rows are inserted in the source model below H:
00348   //
00349   // - A
00350   // - B
00351   // - - C
00352   // - - D
00353   // - - - E
00354   // - - - F
00355   // - - - G
00356   // - H
00357   // - - J
00358   // - - K
00359   // - - - L
00360   // - I
00361   //
00362   // As L matches the filter, it should be part of the KRecursiveFilterProxyModel.
00363   //
00364   // - A
00365   // - B
00366   // - - D
00367   // - - - F
00368   // - H
00369   // - - K
00370   // - - - L
00371   //
00372   // when the QSortFilterProxyModel gets a notification about new rows in H, it only checks
00373   // J and K to see if they match, ignoring L, and therefore not adding it to the proxy.
00374   // To work around that, we make sure that the QSFPM slot which handles that change in
00375   // the source model (_q_sourceRowsAboutToBeInserted) does not get called directly.
00376   // Instead we connect the sourceModel signal to our own slot in *this (sourceRowsAboutToBeInserted)
00377   // Inside that method, the entire new subtree is queried (J, K *and* L) to see if there is a match,
00378   // then the relevant slots in QSFPM are invoked.
00379   // In the example above, we need to tell the QSFPM that H should be queried again to see if
00380   // it matches the filter. It did not before, because L did not exist before. Now it does. That is
00381   // achieved by telling the QSFPM that the data changed for H, which causes it to requery this class
00382   // to see if H matches the filter (which it now does as L now exists).
00383   // That is done in refreshAscendantMapping.
00384 
00385   disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
00386       this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex)));
00387 
00388   disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
00389       this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int)));
00390 
00391   disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
00392       this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int)));
00393 
00394   disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
00395       this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
00396 
00397   disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
00398       this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int)));
00399 
00400   // Slots for manual invoking of QSortFilterProxyModel methods.
00401   connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
00402       this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
00403 
00404   connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
00405       this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
00406 
00407   connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
00408       this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
00409 
00410   connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
00411       this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
00412 
00413   connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
00414       this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
00415 
00416 }
00417 
00418 #include "krecursivefilterproxymodel.moc"

akonadi

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

KDE-PIM Libraries

Skip menu "KDE-PIM Libraries"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kblog
  • kcal
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Generated for KDE-PIM Libraries by doxygen 1.7.1
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