• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.14.38 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
34class KRecursiveFilterProxyModelPrivate
35{
36 Q_DECLARE_PUBLIC(KRecursiveFilterProxyModel)
37 KRecursiveFilterProxyModel *q_ptr;
38public:
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
120void 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
143QModelIndex 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
156void 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
172void 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
204void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
205{
206 invokeRowsAboutToBeRemoved(source_parent, start, end);
207}
208
209void 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
234KRecursiveFilterProxyModel::KRecursiveFilterProxyModel(QObject* parent)
235 : QSortFilterProxyModel(parent), d_ptr(new KRecursiveFilterProxyModelPrivate(this))
236{
237 setDynamicSortFilter(true);
238}
239
240KRecursiveFilterProxyModel::~KRecursiveFilterProxyModel()
241{
242 delete d_ptr;
243}
244
245bool 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
266QModelIndexList 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
282bool KRecursiveFilterProxyModel::acceptRow(int sourceRow, const QModelIndex& sourceParent) const
283{
284 return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
285}
286
287void 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"
KRecursiveFilterProxyModel
Implements recursive filtering of models.
Definition: krecursivefilterproxymodel.h:88
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
@reimplemented
Definition: krecursivefilterproxymodel.cpp:266
KRecursiveFilterProxyModel::KRecursiveFilterProxyModel
KRecursiveFilterProxyModel(QObject *parent=0)
Constructor.
Definition: krecursivefilterproxymodel.cpp:234
KRecursiveFilterProxyModel::~KRecursiveFilterProxyModel
virtual ~KRecursiveFilterProxyModel()
Destructor.
Definition: krecursivefilterproxymodel.cpp:240
KRecursiveFilterProxyModel::d_ptr
KRecursiveFilterProxyModelPrivate *const d_ptr
Definition: krecursivefilterproxymodel.h:121
KRecursiveFilterProxyModel::acceptRow
virtual bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const
Reimplement this method for custom filtering strategies.
Definition: krecursivefilterproxymodel.cpp:282
KRecursiveFilterProxyModel::setSourceModel
void setSourceModel(QAbstractItemModel *model)
Definition: krecursivefilterproxymodel.cpp:287
QAbstractItemModel
QObject
QSortFilterProxyModel
kdebug.h
krecursivefilterproxymodel.h
KStandardShortcut::end
const KShortcut & end()
Goto end of the document.
Definition: kstandardshortcut.cpp:348
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 20 2023 00:00:00 by doxygen 1.9.6 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.38 API Reference

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