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

KDEUI

  • 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  {
44  qRegisterMetaType<QModelIndex>( "QModelIndex" );
45  }
46 
47  // Convenience methods for invoking the QSFPM slots. Those slots must be invoked with invokeMethod
48  // because they are Q_PRIVATE_SLOTs
49  inline void invokeDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
50  {
51  Q_Q(KRecursiveFilterProxyModel);
52  bool success = QMetaObject::invokeMethod(q, "_q_sourceDataChanged", Qt::DirectConnection,
53  Q_ARG(QModelIndex, topLeft),
54  Q_ARG(QModelIndex, bottomRight));
55  Q_UNUSED(success);
56  Q_ASSERT(success);
57  }
58 
59  inline void invokeRowsInserted(const QModelIndex &source_parent, int start, int end)
60  {
61  Q_Q(KRecursiveFilterProxyModel);
62  bool success = QMetaObject::invokeMethod(q, "_q_sourceRowsInserted", Qt::DirectConnection,
63  Q_ARG(QModelIndex, source_parent),
64  Q_ARG(int, start),
65  Q_ARG(int, end));
66  Q_UNUSED(success);
67  Q_ASSERT(success);
68  }
69 
70  inline void invokeRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
71  {
72  Q_Q(KRecursiveFilterProxyModel);
73  bool success = QMetaObject::invokeMethod(q, "_q_sourceRowsAboutToBeInserted", Qt::DirectConnection,
74  Q_ARG(QModelIndex, source_parent),
75  Q_ARG(int, start),
76  Q_ARG(int, end));
77  Q_UNUSED(success);
78  Q_ASSERT(success);
79  }
80 
81  inline void invokeRowsRemoved(const QModelIndex &source_parent, int start, int end)
82  {
83  Q_Q(KRecursiveFilterProxyModel);
84  bool success = QMetaObject::invokeMethod(q, "_q_sourceRowsRemoved", Qt::DirectConnection,
85  Q_ARG(QModelIndex, source_parent),
86  Q_ARG(int, start),
87  Q_ARG(int, end));
88  Q_UNUSED(success);
89  Q_ASSERT(success);
90  }
91 
92  inline void invokeRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
93  {
94  Q_Q(KRecursiveFilterProxyModel);
95  bool success = QMetaObject::invokeMethod(q, "_q_sourceRowsAboutToBeRemoved", Qt::DirectConnection,
96  Q_ARG(QModelIndex, source_parent),
97  Q_ARG(int, start),
98  Q_ARG(int, end));
99  Q_UNUSED(success);
100  Q_ASSERT(success);
101  }
102 
103  void sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right);
104  void sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end);
105  void sourceRowsInserted(const QModelIndex &source_parent, int start, int end);
106  void sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end);
107  void sourceRowsRemoved(const QModelIndex &source_parent, int start, int end);
108 
112  void refreshAscendantMapping(const QModelIndex &index);
113 
114  QModelIndex lastFilteredOutAscendant(const QModelIndex &index);
115 
116  bool ignoreRemove;
117  bool completeInsert;
118 
119  QModelIndex lastHiddenAscendantForInsert;
120 };
121 
122 void KRecursiveFilterProxyModelPrivate::sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right)
123 {
124  QModelIndex source_parent = source_top_left.parent();
125  Q_ASSERT(source_bottom_right.parent() == source_parent); // don't know how to handle different parents in this code...
126 
127  // Tell the world.
128  invokeDataChanged(source_top_left, source_bottom_right);
129 
130  // We can't find out if the change really matters to us or not, for a lack of a dataAboutToBeChanged signal (or a cache).
131  // TODO: add a set of roles that we care for, so we can at least ignore the rest.
132 
133  // Even if we knew the visibility was just toggled, we also can't find out what
134  // was the last filtered out ascendant (on show, like sourceRowsAboutToBeInserted does)
135  // or the last to-be-filtered-out ascendant (on hide, like sourceRowsRemoved does)
136  // So we have to refresh all parents.
137  QModelIndex sourceParent = source_parent;
138  while(sourceParent.isValid())
139  {
140  invokeDataChanged(sourceParent, sourceParent);
141  sourceParent = sourceParent.parent();
142  }
143 }
144 
145 QModelIndex KRecursiveFilterProxyModelPrivate::lastFilteredOutAscendant(const QModelIndex &idx)
146 {
147  Q_Q(KRecursiveFilterProxyModel);
148  QModelIndex last = idx;
149  QModelIndex index = idx.parent();
150  while(index.isValid() && !q->filterAcceptsRow(index.row(), index.parent()))
151  {
152  last = index;
153  index = index.parent();
154  }
155  return last;
156 }
157 
158 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
159 {
160  Q_Q(KRecursiveFilterProxyModel);
161 
162  if (!source_parent.isValid() || q->filterAcceptsRow(source_parent.row(), source_parent.parent()))
163  {
164  // If the parent is already in the model (directly or indirectly), we can just pass on the signal.
165  invokeRowsAboutToBeInserted(source_parent, start, end);
166  completeInsert = true;
167  } else {
168  // OK, so parent is not in the model.
169  // Maybe the grand parent neither.. Go up until the first one that is.
170  lastHiddenAscendantForInsert = lastFilteredOutAscendant(source_parent);
171  }
172 }
173 
174 void KRecursiveFilterProxyModelPrivate::sourceRowsInserted(const QModelIndex &source_parent, int start, int end)
175 {
176  Q_Q(KRecursiveFilterProxyModel);
177 
178  if (completeInsert)
179  {
180  // If the parent is already in the model, we can just pass on the signal.
181  completeInsert = false;
182  invokeRowsInserted(source_parent, start, end);
183  return;
184  }
185 
186  bool requireRow = false;
187  for (int row = start; row <= end; ++row)
188  {
189  if (q->filterAcceptsRow(row, source_parent))
190  {
191  requireRow = true;
192  break;
193  }
194  }
195 
196  if (!requireRow)
197  {
198  // The new rows doesn't have any descendants that match the filter. Filter them out.
199  return;
200  }
201 
202  // Make QSFPM realize that lastHiddenAscendantForInsert should be shown now
203  invokeDataChanged(lastHiddenAscendantForInsert, lastHiddenAscendantForInsert);
204 }
205 
206 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
207 {
208  Q_Q(KRecursiveFilterProxyModel);
209 
210  bool accepted = false;
211  for (int row = start; row <= end; ++row)
212  {
213  if (q->filterAcceptsRow(row, source_parent))
214  {
215  accepted = true;
216  break;
217  }
218  }
219  if (!accepted)
220  {
221  // All removed rows are already filtered out. We don't care about the signal.
222  ignoreRemove = true;
223  return;
224  }
225 
226  invokeRowsAboutToBeRemoved(source_parent, start, end);
227 }
228 
229 void KRecursiveFilterProxyModelPrivate::sourceRowsRemoved(const QModelIndex &source_parent, int start, int end)
230 {
231  Q_Q(KRecursiveFilterProxyModel);
232 
233  if (ignoreRemove)
234  {
235  ignoreRemove = false;
236  return;
237  }
238 
239  invokeRowsRemoved(source_parent, start, end);
240 
241  // Find out if removing this visible row means that some ascendant
242  // row can now be hidden.
243  // We go up until we find a row that should still be visible
244  // and then make QSFPM re-evaluate the last one we saw before that, to hide it.
245 
246  QModelIndex toHide;
247  QModelIndex sourceAscendant = source_parent;
248  while(sourceAscendant.isValid())
249  {
250  if (q->filterAcceptsRow(sourceAscendant.row(), sourceAscendant.parent())) {
251  break;
252  }
253  toHide = sourceAscendant;
254  sourceAscendant = sourceAscendant.parent();
255  }
256  if (toHide.isValid())
257  invokeDataChanged(toHide, toHide);
258 }
259 
260 KRecursiveFilterProxyModel::KRecursiveFilterProxyModel(QObject* parent)
261  : QSortFilterProxyModel(parent), d_ptr(new KRecursiveFilterProxyModelPrivate(this))
262 {
263  setDynamicSortFilter(true);
264 }
265 
266 KRecursiveFilterProxyModel::~KRecursiveFilterProxyModel()
267 {
268  delete d_ptr;
269 }
270 
271 bool KRecursiveFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
272 {
273  // TODO: Implement some caching so that if one match is found on the first pass, we can return early results
274  // when the subtrees are checked by QSFPM.
275  if (acceptRow(sourceRow, sourceParent))
276  return true;
277 
278  QModelIndex source_index = sourceModel()->index(sourceRow, 0, sourceParent);
279  Q_ASSERT(source_index.isValid());
280  bool accepted = false;
281 
282  for (int row = 0, rows = sourceModel()->rowCount(source_index); row < rows; ++row) {
283  if (filterAcceptsRow(row, source_index)) {
284  accepted = true;
285  break;
286  }
287  }
288 
289  return accepted;
290 }
291 
292 QModelIndexList KRecursiveFilterProxyModel::match( const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags ) const
293 {
294  if ( role < Qt::UserRole )
295  return QSortFilterProxyModel::match( start, role, value, hits, flags );
296 
297  QModelIndexList list;
298  QModelIndex proxyIndex;
299  foreach ( const QModelIndex &idx, sourceModel()->match( mapToSource( start ), role, value, hits, flags ) ) {
300  proxyIndex = mapFromSource( idx );
301  if ( proxyIndex.isValid() )
302  list << proxyIndex;
303  }
304 
305  return list;
306 }
307 
308 bool KRecursiveFilterProxyModel::acceptRow(int sourceRow, const QModelIndex& sourceParent) const
309 {
310  return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
311 }
312 
313 void KRecursiveFilterProxyModel::setSourceModel(QAbstractItemModel* model)
314 {
315  // Standard disconnect.
316  disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
317  this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
318 
319  disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
320  this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
321 
322  disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
323  this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
324 
325  disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
326  this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
327 
328  disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
329  this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
330 
331  QSortFilterProxyModel::setSourceModel(model);
332 
333  // Disconnect in the QSortFilterProxyModel. These methods will be invoked manually
334  // in invokeDataChanged, invokeRowsInserted etc.
335  //
336  // The reason for that is that when the source model adds new rows for example, the new rows
337  // May not match the filter, but maybe their child items do match.
338  //
339  // Source model before insert:
340  //
341  // - A
342  // - B
343  // - - C
344  // - - D
345  // - - - E
346  // - - - F
347  // - - - G
348  // - H
349  // - I
350  //
351  // If the A F and L (which doesn't exist in the source model yet) match the filter
352  // the proxy will be:
353  //
354  // - A
355  // - B
356  // - - D
357  // - - - F
358  //
359  // New rows are inserted in the source model below H:
360  //
361  // - A
362  // - B
363  // - - C
364  // - - D
365  // - - - E
366  // - - - F
367  // - - - G
368  // - H
369  // - - J
370  // - - K
371  // - - - L
372  // - I
373  //
374  // As L matches the filter, it should be part of the KRecursiveFilterProxyModel.
375  //
376  // - A
377  // - B
378  // - - D
379  // - - - F
380  // - H
381  // - - K
382  // - - - L
383  //
384  // when the QSortFilterProxyModel gets a notification about new rows in H, it only checks
385  // J and K to see if they match, ignoring L, and therefore not adding it to the proxy.
386  // To work around that, we make sure that the QSFPM slot which handles that change in
387  // the source model (_q_sourceRowsAboutToBeInserted) does not get called directly.
388  // Instead we connect the sourceModel signal to our own slot in *this (sourceRowsAboutToBeInserted)
389  // Inside that method, the entire new subtree is queried (J, K *and* L) to see if there is a match,
390  // then the relevant slots in QSFPM are invoked.
391  // In the example above, we need to tell the QSFPM that H should be queried again to see if
392  // it matches the filter. It did not before, because L did not exist before. Now it does. That is
393  // achieved by telling the QSFPM that the data changed for H, which causes it to requery this class
394  // to see if H matches the filter (which it now does as L now exists).
395  // That is done in sourceRowsInserted.
396 
397  disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
398  this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex)));
399 
400  disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
401  this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int)));
402 
403  disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
404  this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int)));
405 
406  disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
407  this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
408 
409  disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
410  this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int)));
411 
412  // Slots for manual invoking of QSortFilterProxyModel methods.
413  connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
414  this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
415 
416  connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
417  this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
418 
419  connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
420  this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
421 
422  connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
423  this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
424 
425  connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
426  this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
427 
428 }
429 
430 #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:308
QObject
KRecursiveFilterProxyModel::~KRecursiveFilterProxyModel
virtual ~KRecursiveFilterProxyModel()
Destructor.
Definition: krecursivefilterproxymodel.cpp:266
QAbstractItemModel
KRecursiveFilterProxyModel
Implements recursive filtering of models.
Definition: krecursivefilterproxymodel.h:87
QSortFilterProxyModel
KRecursiveFilterProxyModel::KRecursiveFilterProxyModel
KRecursiveFilterProxyModel(QObject *parent=0)
Constructor.
Definition: krecursivefilterproxymodel.cpp:260
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:292
KRecursiveFilterProxyModel::d_ptr
KRecursiveFilterProxyModelPrivate *const d_ptr
Definition: krecursivefilterproxymodel.h:121
KRecursiveFilterProxyModel::setSourceModel
void setSourceModel(QAbstractItemModel *model)
Definition: krecursivefilterproxymodel.cpp:313
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-2017 The KDE developers.
Generated on Tue May 23 2017 16:52:28 by doxygen 1.8.5 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-4.14.8 API Reference

Skip menu "kdelibs-4.14.8 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
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
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