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

KDEUI

  • sources
  • kde-4.12
  • kdelibs
  • kdeui
  • itemviews
krecursivefilterproxymodel.cpp
Go to the documentation of this file.
1 /*
2  Copyright (c) 2009 Stephen Kelly <steveire@gmail.com>
3 
4  This library is free software; you can redistribute it and/or modify it
5  under the terms of the GNU Library General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or (at your
7  option) any later version.
8 
9  This library is distributed in the hope that it will be useful, but WITHOUT
10  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12  License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to the
16  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  02110-1301, USA.
18 */
19 
20 #include "krecursivefilterproxymodel.h"
21 
22 #include <kdebug.h>
23 
24 // Maintainability note:
25 // This class invokes some Q_PRIVATE_SLOTs in QSortFilterProxyModel which are
26 // private API and could be renamed or removed at any time.
27 // If they are renamed, the invokations can be updated with an #if (QT_VERSION(...))
28 // If they are removed, then layout{AboutToBe}Changed signals should be used when the source model
29 // gets new rows or has rowsremoved or moved. The Q_PRIVATE_SLOT invokation is an optimization
30 // because layout{AboutToBe}Changed is expensive and causes the entire mapping of the tree in QSFPM
31 // to be cleared, even if only a part of it is dirty.
32 // Stephen Kelly, 30 April 2010.
33 
34 class KRecursiveFilterProxyModelPrivate
35 {
36  Q_DECLARE_PUBLIC(KRecursiveFilterProxyModel)
37  KRecursiveFilterProxyModel *q_ptr;
38 public:
39  KRecursiveFilterProxyModelPrivate(KRecursiveFilterProxyModel *model)
40  : q_ptr(model),
41  ignoreRemove(false),
42  completeInsert(false),
43  completeRemove(false)
44  {
45  qRegisterMetaType<QModelIndex>( "QModelIndex" );
46  }
47 
48  // Convenience methods for invoking the QSFPM slots. Those slots must be invoked with invokeMethod
49  // because they are Q_PRIVATE_SLOTs
50  inline void invokeDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
51  {
52  Q_Q(KRecursiveFilterProxyModel);
53  bool success = QMetaObject::invokeMethod(q, "_q_sourceDataChanged", Qt::DirectConnection,
54  Q_ARG(QModelIndex, topLeft),
55  Q_ARG(QModelIndex, bottomRight));
56  Q_UNUSED(success);
57  Q_ASSERT(success);
58  }
59 
60  inline void invokeRowsInserted(const QModelIndex &source_parent, int start, int end)
61  {
62  Q_Q(KRecursiveFilterProxyModel);
63  bool success = QMetaObject::invokeMethod(q, "_q_sourceRowsInserted", Qt::DirectConnection,
64  Q_ARG(QModelIndex, source_parent),
65  Q_ARG(int, start),
66  Q_ARG(int, end));
67  Q_UNUSED(success);
68  Q_ASSERT(success);
69  }
70 
71  inline void invokeRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
72  {
73  Q_Q(KRecursiveFilterProxyModel);
74  bool success = QMetaObject::invokeMethod(q, "_q_sourceRowsAboutToBeInserted", Qt::DirectConnection,
75  Q_ARG(QModelIndex, source_parent),
76  Q_ARG(int, start),
77  Q_ARG(int, end));
78  Q_UNUSED(success);
79  Q_ASSERT(success);
80  }
81 
82  inline void invokeRowsRemoved(const QModelIndex &source_parent, int start, int end)
83  {
84  Q_Q(KRecursiveFilterProxyModel);
85  bool success = QMetaObject::invokeMethod(q, "_q_sourceRowsRemoved", Qt::DirectConnection,
86  Q_ARG(QModelIndex, source_parent),
87  Q_ARG(int, start),
88  Q_ARG(int, end));
89  Q_UNUSED(success);
90  Q_ASSERT(success);
91  }
92 
93  inline void invokeRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
94  {
95  Q_Q(KRecursiveFilterProxyModel);
96  bool success = QMetaObject::invokeMethod(q, "_q_sourceRowsAboutToBeRemoved", Qt::DirectConnection,
97  Q_ARG(QModelIndex, source_parent),
98  Q_ARG(int, start),
99  Q_ARG(int, end));
100  Q_UNUSED(success);
101  Q_ASSERT(success);
102  }
103 
104  void sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right);
105  void sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end);
106  void sourceRowsInserted(const QModelIndex &source_parent, int start, int end);
107  void sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end);
108  void sourceRowsRemoved(const QModelIndex &source_parent, int start, int end);
109 
116  void refreshAscendantMapping(const QModelIndex &index, bool refreshAll = false);
117 
118  bool ignoreRemove;
119  bool completeInsert;
120  bool completeRemove;
121 };
122 
123 void KRecursiveFilterProxyModelPrivate::sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right)
124 {
125  Q_Q(KRecursiveFilterProxyModel);
126 
127  QModelIndex source_parent = source_top_left.parent();
128 
129  if (!source_parent.isValid() || q->filterAcceptsRow(source_parent.row(), source_parent.parent()))
130  {
131  invokeDataChanged(source_top_left, source_bottom_right);
132  return;
133  }
134 
135  bool requireRow = false;
136  for (int row = source_top_left.row(); row <= source_bottom_right.row(); ++row)
137  if (q->filterAcceptsRow(row, source_parent))
138  {
139  requireRow = true;
140  break;
141  }
142 
143  if (!requireRow) // None of the changed rows are now required in the model.
144  return;
145 
146  refreshAscendantMapping(source_parent);
147 }
148 
149 void KRecursiveFilterProxyModelPrivate::refreshAscendantMapping(const QModelIndex &index, bool refreshAll)
150 {
151  Q_Q(KRecursiveFilterProxyModel);
152 
153  Q_ASSERT(index.isValid());
154  QModelIndex lastAscendant = index;
155  QModelIndex sourceAscendant = index.parent();
156  // We got a matching descendant, so find the right place to insert the row.
157  // We need to tell the QSortFilterProxyModel that the first child between an existing row in the model
158  // has changed data so that it will get a mapping.
159  while(sourceAscendant.isValid() && !q->acceptRow(sourceAscendant.row(), sourceAscendant.parent()))
160  {
161  if (refreshAll)
162  invokeDataChanged(sourceAscendant, sourceAscendant);
163 
164  lastAscendant = sourceAscendant;
165  sourceAscendant = sourceAscendant.parent();
166  }
167 
168  // Inform the model that its data changed so that it creates new mappings and finds the rows which now match the filter.
169  invokeDataChanged(lastAscendant, lastAscendant);
170 }
171 
172 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
173 {
174  Q_Q(KRecursiveFilterProxyModel);
175 
176  if (!source_parent.isValid() || q->filterAcceptsRow(source_parent.row(), source_parent.parent()))
177  {
178  invokeRowsAboutToBeInserted(source_parent, start, end);
179  completeInsert = true;
180  }
181 }
182 
183 void KRecursiveFilterProxyModelPrivate::sourceRowsInserted(const QModelIndex &source_parent, int start, int end)
184 {
185  Q_Q(KRecursiveFilterProxyModel);
186 
187  if (completeInsert)
188  {
189  completeInsert = false;
190  invokeRowsInserted(source_parent, start, end);
191  // If the parent is already in the model, we can just pass on the signal.
192  return;
193  }
194 
195  bool requireRow = false;
196  for (int row = start; row <= end; ++row)
197  {
198  if (q->filterAcceptsRow(row, source_parent))
199  {
200  requireRow = true;
201  break;
202  }
203  }
204 
205  if (!requireRow)
206  {
207  // The row doesn't have descendants that match the filter. Filter it out.
208  return;
209  }
210 
211  refreshAscendantMapping(source_parent);
212 }
213 
214 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
215 {
216  Q_Q(KRecursiveFilterProxyModel);
217 
218  if (source_parent.isValid() && q->filterAcceptsRow(source_parent.row(), source_parent.parent()))
219  {
220  invokeRowsAboutToBeRemoved(source_parent, start, end);
221  completeRemove = true;
222  return;
223  }
224 
225  bool accepted = false;
226  for (int row = start; row <= end; ++row)
227  {
228  if (q->filterAcceptsRow(row, source_parent))
229  {
230  accepted = true;
231  break;
232  }
233  }
234  if (!accepted)
235  {
236  // All removed rows are already filtered out. We don't care about the signal.
237  ignoreRemove = true;
238  return;
239  }
240  completeRemove = true;
241  invokeRowsAboutToBeRemoved(source_parent, start, end);
242 }
243 
244 void KRecursiveFilterProxyModelPrivate::sourceRowsRemoved(const QModelIndex &source_parent, int start, int end)
245 {
246  if (completeRemove)
247  {
248  completeRemove = false;
249  // Source parent is already in the model.
250  invokeRowsRemoved(source_parent, start, end);
251  // fall through. After removing rows, we need to refresh things so that intermediates will be removed too if necessary.
252  }
253 
254  if (ignoreRemove)
255  {
256  ignoreRemove = false;
257  return;
258  }
259 
260  // Refresh intermediate rows too.
261  // This is needed because QSFPM only invalidates the mapping for the
262  // index range given to dataChanged, not its children.
263  if (source_parent.isValid())
264  refreshAscendantMapping(source_parent, true);
265 }
266 
267 KRecursiveFilterProxyModel::KRecursiveFilterProxyModel(QObject* parent)
268  : QSortFilterProxyModel(parent), d_ptr(new KRecursiveFilterProxyModelPrivate(this))
269 {
270  setDynamicSortFilter(true);
271 }
272 
273 KRecursiveFilterProxyModel::~KRecursiveFilterProxyModel()
274 {
275  delete d_ptr;
276 }
277 
278 bool KRecursiveFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
279 {
280  // TODO: Implement some caching so that if one match is found on the first pass, we can return early results
281  // when the subtrees are checked by QSFPM.
282  if (acceptRow(sourceRow, sourceParent))
283  return true;
284 
285  QModelIndex source_index = sourceModel()->index(sourceRow, 0, sourceParent);
286  Q_ASSERT(source_index.isValid());
287  bool accepted = false;
288 
289  for (int row = 0 ; row < sourceModel()->rowCount(source_index); ++row)
290  if (filterAcceptsRow(row, source_index))
291  accepted = true; // Need to do this in a loop so that all siblings in a parent get processed, not just the first.
292 
293  return accepted;
294 }
295 
296 QModelIndexList KRecursiveFilterProxyModel::match( const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags ) const
297 {
298  if ( role < Qt::UserRole )
299  return QSortFilterProxyModel::match( start, role, value, hits, flags );
300 
301  QModelIndexList list;
302  QModelIndex proxyIndex;
303  foreach ( const QModelIndex &idx, sourceModel()->match( mapToSource( start ), role, value, hits, flags ) ) {
304  proxyIndex = mapFromSource( idx );
305  if ( proxyIndex.isValid() )
306  list << proxyIndex;
307  }
308 
309  return list;
310 }
311 
312 bool KRecursiveFilterProxyModel::acceptRow(int sourceRow, const QModelIndex& sourceParent) const
313 {
314  return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
315 }
316 
317 void KRecursiveFilterProxyModel::setSourceModel(QAbstractItemModel* model)
318 {
319  // Standard disconnect.
320  disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
321  this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
322 
323  disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
324  this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
325 
326  disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
327  this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
328 
329  disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
330  this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
331 
332  disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
333  this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
334 
335  QSortFilterProxyModel::setSourceModel(model);
336 
337  // Disconnect in the QSortFilterProxyModel. These methods will be invoked manually
338  // in invokeDataChanged, invokeRowsInserted etc.
339  //
340  // The reason for that is that when the source model adds new rows for example, the new rows
341  // May not match the filter, but maybe their child items do match.
342  //
343  // Source model before insert:
344  //
345  // - A
346  // - B
347  // - - C
348  // - - D
349  // - - - E
350  // - - - F
351  // - - - G
352  // - H
353  // - I
354  //
355  // If the A F and L (which doesn't exist in the source model yet) match the filter
356  // the proxy will be:
357  //
358  // - A
359  // - B
360  // - - D
361  // - - - F
362  //
363  // New rows are inserted in the source model below H:
364  //
365  // - A
366  // - B
367  // - - C
368  // - - D
369  // - - - E
370  // - - - F
371  // - - - G
372  // - H
373  // - - J
374  // - - K
375  // - - - L
376  // - I
377  //
378  // As L matches the filter, it should be part of the KRecursiveFilterProxyModel.
379  //
380  // - A
381  // - B
382  // - - D
383  // - - - F
384  // - H
385  // - - K
386  // - - - L
387  //
388  // when the QSortFilterProxyModel gets a notification about new rows in H, it only checks
389  // J and K to see if they match, ignoring L, and therefore not adding it to the proxy.
390  // To work around that, we make sure that the QSFPM slot which handles that change in
391  // the source model (_q_sourceRowsAboutToBeInserted) does not get called directly.
392  // Instead we connect the sourceModel signal to our own slot in *this (sourceRowsAboutToBeInserted)
393  // Inside that method, the entire new subtree is queried (J, K *and* L) to see if there is a match,
394  // then the relevant slots in QSFPM are invoked.
395  // In the example above, we need to tell the QSFPM that H should be queried again to see if
396  // it matches the filter. It did not before, because L did not exist before. Now it does. That is
397  // achieved by telling the QSFPM that the data changed for H, which causes it to requery this class
398  // to see if H matches the filter (which it now does as L now exists).
399  // That is done in refreshAscendantMapping.
400 
401  disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
402  this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex)));
403 
404  disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
405  this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int)));
406 
407  disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
408  this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int)));
409 
410  disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
411  this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
412 
413  disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
414  this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int)));
415 
416  // Slots for manual invoking of QSortFilterProxyModel methods.
417  connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
418  this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
419 
420  connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
421  this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
422 
423  connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
424  this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
425 
426  connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
427  this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
428 
429  connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
430  this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
431 
432 }
433 
434 #include "krecursivefilterproxymodel.moc"
QVariant
kdebug.h
KRecursiveFilterProxyModel::acceptRow
virtual bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const
Reimplement this method for custom filtering strategies.
Definition: krecursivefilterproxymodel.cpp:312
QObject
KRecursiveFilterProxyModel::~KRecursiveFilterProxyModel
virtual ~KRecursiveFilterProxyModel()
Destructor.
Definition: krecursivefilterproxymodel.cpp:273
QAbstractItemModel
KRecursiveFilterProxyModel
Implements recursive filtering of models.
Definition: krecursivefilterproxymodel.h:87
QSortFilterProxyModel
KRecursiveFilterProxyModel::KRecursiveFilterProxyModel
KRecursiveFilterProxyModel(QObject *parent=0)
Constructor.
Definition: krecursivefilterproxymodel.cpp:267
KRecursiveFilterProxyModel::match
virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits=1, Qt::MatchFlags flags=Qt::MatchFlags(Qt::MatchStartsWith|Qt::MatchWrap)) const
Definition: krecursivefilterproxymodel.cpp:296
KRecursiveFilterProxyModel::d_ptr
KRecursiveFilterProxyModelPrivate *const d_ptr
Definition: krecursivefilterproxymodel.h:121
KRecursiveFilterProxyModel::setSourceModel
void setSourceModel(QAbstractItemModel *model)
Definition: krecursivefilterproxymodel.cpp:317
KStandardShortcut::end
const KShortcut & end()
Goto end of the document.
Definition: kstandardshortcut.cpp:348
krecursivefilterproxymodel.h
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 22:49:15 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KDEUI

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

kdelibs API Reference

Skip menu "kdelibs API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  • kjsembed
  •   WTF
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Nepomuk-Core
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal