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

KDEUI

  • home
  • ichiro
  • data
  • ssd
  • Momonga
  • trunk
  • pkgs
  • kdelibs
  • BUILD
  • kdelibs-4.14.3
  • 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->acceptRow(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  Q_ASSERT(index.isValid());
153 
154  QModelIndex sourceAscendant = index;
155  // We got a matching descendant, so find the right place to insert the row.
156  // We need to tell the QSortFilterProxyModel that the first child between an existing row in the model
157  // has changed data so that it will get a mapping.
158  while(sourceAscendant.isValid())
159  {
160  invokeDataChanged(sourceAscendant, sourceAscendant);
161  sourceAscendant = sourceAscendant.parent();
162  }
163 }
164 
165 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
166 {
167  Q_Q(KRecursiveFilterProxyModel);
168 
169  if (!source_parent.isValid() || q->filterAcceptsRow(source_parent.row(), source_parent.parent()))
170  {
171  invokeRowsAboutToBeInserted(source_parent, start, end);
172  completeInsert = true;
173  }
174 }
175 
176 void KRecursiveFilterProxyModelPrivate::sourceRowsInserted(const QModelIndex &source_parent, int start, int end)
177 {
178  Q_Q(KRecursiveFilterProxyModel);
179 
180  if (completeInsert)
181  {
182  completeInsert = false;
183  invokeRowsInserted(source_parent, start, end);
184  // If the parent is already in the model, we can just pass on the signal.
185  return;
186  }
187 
188  bool requireRow = false;
189  for (int row = start; row <= end; ++row)
190  {
191  if (q->filterAcceptsRow(row, source_parent))
192  {
193  requireRow = true;
194  break;
195  }
196  }
197 
198  if (!requireRow)
199  {
200  // The row doesn't have descendants that match the filter. Filter it out.
201  return;
202  }
203 
204  refreshAscendantMapping(source_parent);
205 }
206 
207 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
208 {
209  Q_Q(KRecursiveFilterProxyModel);
210 
211  if (source_parent.isValid() && q->filterAcceptsRow(source_parent.row(), source_parent.parent()))
212  {
213  invokeRowsAboutToBeRemoved(source_parent, start, end);
214  completeRemove = true;
215  return;
216  }
217 
218  bool accepted = false;
219  for (int row = start; row <= end; ++row)
220  {
221  if (q->filterAcceptsRow(row, source_parent))
222  {
223  accepted = true;
224  break;
225  }
226  }
227  if (!accepted)
228  {
229  // All removed rows are already filtered out. We don't care about the signal.
230  ignoreRemove = true;
231  return;
232  }
233  completeRemove = true;
234  invokeRowsAboutToBeRemoved(source_parent, start, end);
235 }
236 
237 void KRecursiveFilterProxyModelPrivate::sourceRowsRemoved(const QModelIndex &source_parent, int start, int end)
238 {
239  if (completeRemove)
240  {
241  completeRemove = false;
242  // Source parent is already in the model.
243  invokeRowsRemoved(source_parent, start, end);
244  // fall through. After removing rows, we need to refresh things so that intermediates will be removed too if necessary.
245  }
246 
247  if (ignoreRemove)
248  {
249  ignoreRemove = false;
250  return;
251  }
252 
253  // Refresh intermediate rows too.
254  // This is needed because QSFPM only invalidates the mapping for the
255  // index range given to dataChanged, not its children.
256  if (source_parent.isValid())
257  refreshAscendantMapping(source_parent, true);
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 ; row < sourceModel()->rowCount(source_index); ++row)
283  if (filterAcceptsRow(row, source_index))
284  accepted = true; // Need to do this in a loop so that all siblings in a parent get processed, not just the first.
285 
286  return accepted;
287 }
288 
289 QModelIndexList KRecursiveFilterProxyModel::match( const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags ) const
290 {
291  if ( role < Qt::UserRole )
292  return QSortFilterProxyModel::match( start, role, value, hits, flags );
293 
294  QModelIndexList list;
295  QModelIndex proxyIndex;
296  foreach ( const QModelIndex &idx, sourceModel()->match( mapToSource( start ), role, value, hits, flags ) ) {
297  proxyIndex = mapFromSource( idx );
298  if ( proxyIndex.isValid() )
299  list << proxyIndex;
300  }
301 
302  return list;
303 }
304 
305 bool KRecursiveFilterProxyModel::acceptRow(int sourceRow, const QModelIndex& sourceParent) const
306 {
307  return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
308 }
309 
310 void KRecursiveFilterProxyModel::setSourceModel(QAbstractItemModel* model)
311 {
312  // Standard disconnect.
313  disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
314  this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
315 
316  disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
317  this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
318 
319  disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
320  this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
321 
322  disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
323  this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
324 
325  disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
326  this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
327 
328  QSortFilterProxyModel::setSourceModel(model);
329 
330  // Disconnect in the QSortFilterProxyModel. These methods will be invoked manually
331  // in invokeDataChanged, invokeRowsInserted etc.
332  //
333  // The reason for that is that when the source model adds new rows for example, the new rows
334  // May not match the filter, but maybe their child items do match.
335  //
336  // Source model before insert:
337  //
338  // - A
339  // - B
340  // - - C
341  // - - D
342  // - - - E
343  // - - - F
344  // - - - G
345  // - H
346  // - I
347  //
348  // If the A F and L (which doesn't exist in the source model yet) match the filter
349  // the proxy will be:
350  //
351  // - A
352  // - B
353  // - - D
354  // - - - F
355  //
356  // New rows are inserted in the source model below H:
357  //
358  // - A
359  // - B
360  // - - C
361  // - - D
362  // - - - E
363  // - - - F
364  // - - - G
365  // - H
366  // - - J
367  // - - K
368  // - - - L
369  // - I
370  //
371  // As L matches the filter, it should be part of the KRecursiveFilterProxyModel.
372  //
373  // - A
374  // - B
375  // - - D
376  // - - - F
377  // - H
378  // - - K
379  // - - - L
380  //
381  // when the QSortFilterProxyModel gets a notification about new rows in H, it only checks
382  // J and K to see if they match, ignoring L, and therefore not adding it to the proxy.
383  // To work around that, we make sure that the QSFPM slot which handles that change in
384  // the source model (_q_sourceRowsAboutToBeInserted) does not get called directly.
385  // Instead we connect the sourceModel signal to our own slot in *this (sourceRowsAboutToBeInserted)
386  // Inside that method, the entire new subtree is queried (J, K *and* L) to see if there is a match,
387  // then the relevant slots in QSFPM are invoked.
388  // In the example above, we need to tell the QSFPM that H should be queried again to see if
389  // it matches the filter. It did not before, because L did not exist before. Now it does. That is
390  // achieved by telling the QSFPM that the data changed for H, which causes it to requery this class
391  // to see if H matches the filter (which it now does as L now exists).
392  // That is done in refreshAscendantMapping.
393 
394  disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
395  this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex)));
396 
397  disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
398  this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int)));
399 
400  disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
401  this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int)));
402 
403  disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
404  this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
405 
406  disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
407  this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int)));
408 
409  // Slots for manual invoking of QSortFilterProxyModel methods.
410  connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
411  this, SLOT(sourceDataChanged(QModelIndex,QModelIndex)));
412 
413  connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
414  this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
415 
416  connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
417  this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
418 
419  connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
420  this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
421 
422  connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
423  this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
424 
425 }
426 
427 #include "krecursivefilterproxymodel.moc"
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:289
kdebug.h
QObject
KRecursiveFilterProxyModel::~KRecursiveFilterProxyModel
virtual ~KRecursiveFilterProxyModel()
Destructor.
Definition: krecursivefilterproxymodel.cpp:266
KRecursiveFilterProxyModel::acceptRow
virtual bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const
Reimplement this method for custom filtering strategies.
Definition: krecursivefilterproxymodel.cpp:305
QAbstractItemModel
KRecursiveFilterProxyModel
Implements recursive filtering of models.
Definition: krecursivefilterproxymodel.h:87
QSortFilterProxyModel
KRecursiveFilterProxyModel::KRecursiveFilterProxyModel
KRecursiveFilterProxyModel(QObject *parent=0)
Constructor.
Definition: krecursivefilterproxymodel.cpp:260
KRecursiveFilterProxyModel::d_ptr
KRecursiveFilterProxyModelPrivate *const d_ptr
Definition: krecursivefilterproxymodel.h:121
KRecursiveFilterProxyModel::setSourceModel
void setSourceModel(QAbstractItemModel *model)
Definition: krecursivefilterproxymodel.cpp:310
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-2018 The KDE developers.
Generated on Fri Oct 19 2018 17:12:35 by doxygen 1.8.13 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.3 API Reference

Skip menu "kdelibs-4.14.3 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