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

KDEUI

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