summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--framework/src/CMakeLists.txt1
-rw-r--r--framework/src/domain/folderlistmodel.cpp20
-rw-r--r--framework/src/domain/folderlistmodel.h7
-rw-r--r--framework/src/krecursivefilterproxymodel.cpp426
-rw-r--r--framework/src/krecursivefilterproxymodel.h135
5 files changed, 583 insertions, 6 deletions
diff --git a/framework/src/CMakeLists.txt b/framework/src/CMakeLists.txt
index 9c845022..86145d5a 100644
--- a/framework/src/CMakeLists.txt
+++ b/framework/src/CMakeLists.txt
@@ -42,6 +42,7 @@ set(SRCS
42 sinkfabric.cpp 42 sinkfabric.cpp
43 kubeimage.cpp 43 kubeimage.cpp
44 clipboardproxy.cpp 44 clipboardproxy.cpp
45 krecursivefilterproxymodel.cpp
45) 46)
46 47
47add_library(frameworkplugin SHARED ${SRCS}) 48add_library(frameworkplugin SHARED ${SRCS})
diff --git a/framework/src/domain/folderlistmodel.cpp b/framework/src/domain/folderlistmodel.cpp
index 0abc70c5..1fe2abe6 100644
--- a/framework/src/domain/folderlistmodel.cpp
+++ b/framework/src/domain/folderlistmodel.cpp
@@ -26,11 +26,20 @@
26using namespace Sink; 26using namespace Sink;
27using namespace Sink::ApplicationDomain; 27using namespace Sink::ApplicationDomain;
28 28
29FolderListModel::FolderListModel(QObject *parent) : QSortFilterProxyModel() 29FolderListModel::FolderListModel(QObject *parent) : KRecursiveFilterProxyModel()
30{ 30{
31 setDynamicSortFilter(true); 31 setDynamicSortFilter(true);
32 sort(0, Qt::AscendingOrder); 32 sort(0, Qt::AscendingOrder);
33 33
34 //Automatically fetch all folders, otherwise the recursive filtering does not work.
35 QObject::connect(this, &QSortFilterProxyModel::sourceModelChanged, [this] () {
36 QObject::connect(sourceModel(), &QAbstractItemModel::rowsInserted, sourceModel(), [this] (QModelIndex parent, int first, int last) {
37 for (int row = first; row <= last; row++) {
38 auto idx = sourceModel()->index(row, 0, parent);
39 sourceModel()->fetchMore(idx);
40 }
41 });
42 });
34} 43}
35 44
36FolderListModel::~FolderListModel() 45FolderListModel::~FolderListModel()
@@ -99,7 +108,6 @@ void FolderListModel::setAccountId(const QVariant &accountId)
99 auto query = Query(); 108 auto query = Query();
100 query.resourceFilter<SinkResource::Account>(account); 109 query.resourceFilter<SinkResource::Account>(account);
101 query.setFlags(Sink::Query::LiveQuery | Sink::Query::UpdateStatus); 110 query.setFlags(Sink::Query::LiveQuery | Sink::Query::UpdateStatus);
102 query.filter<Folder::Enabled>(true);
103 query.request<Folder::Name>() 111 query.request<Folder::Name>()
104 .request<Folder::Icon>() 112 .request<Folder::Icon>()
105 .request<Folder::Parent>() 113 .request<Folder::Parent>()
@@ -144,6 +152,14 @@ bool FolderListModel::lessThan(const QModelIndex &left, const QModelIndex &right
144 return leftPriority < rightPriority; 152 return leftPriority < rightPriority;
145} 153}
146 154
155bool FolderListModel::acceptRow(int sourceRow, const QModelIndex &sourceParent) const
156{
157 auto index = sourceModel()->index(sourceRow, 0, sourceParent);
158 const auto folder = index.data(Sink::Store::DomainObjectRole).value<Sink::ApplicationDomain::Folder::Ptr>();
159 const auto enabled = folder->getEnabled();
160 return enabled;
161}
162
147void FolderListModel::setFolderId(const QVariant &folderId) 163void FolderListModel::setFolderId(const QVariant &folderId)
148{ 164{
149 const auto folder = folderId.toString().toUtf8(); 165 const auto folder = folderId.toString().toUtf8();
diff --git a/framework/src/domain/folderlistmodel.h b/framework/src/domain/folderlistmodel.h
index 0e412202..738cf4a0 100644
--- a/framework/src/domain/folderlistmodel.h
+++ b/framework/src/domain/folderlistmodel.h
@@ -20,16 +20,14 @@
20 20
21#pragma once 21#pragma once
22 22
23#include <QObject> 23#include <krecursivefilterproxymodel.h>
24#include <QSortFilterProxyModel>
25#include <QSharedPointer> 24#include <QSharedPointer>
26#include <QStringList>
27 25
28namespace Sink { 26namespace Sink {
29 class Query; 27 class Query;
30} 28}
31 29
32class FolderListModel : public QSortFilterProxyModel 30class FolderListModel : public KRecursiveFilterProxyModel
33{ 31{
34 Q_OBJECT 32 Q_OBJECT
35 33
@@ -69,6 +67,7 @@ public:
69 QVariant folderId() const; 67 QVariant folderId() const;
70protected: 68protected:
71 bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE; 69 bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE;
70 bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_OVERRIDE;
72 71
73private: 72private:
74 void runQuery(const Sink::Query &query); 73 void runQuery(const Sink::Query &query);
diff --git a/framework/src/krecursivefilterproxymodel.cpp b/framework/src/krecursivefilterproxymodel.cpp
new file mode 100644
index 00000000..014e1076
--- /dev/null
+++ b/framework/src/krecursivefilterproxymodel.cpp
@@ -0,0 +1,426 @@
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 <QMetaMethod>
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 invocations can be updated with an #if (QT_VERSION(...))
28// If they are removed, then layout{AboutToBe}Changed Q_SIGNALS should be used when the source model
29// gets new rows or has rowsremoved or moved. The Q_PRIVATE_SLOT invocation 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// All this is temporary anyway, the long term solution is support in QSFPM: https://codereview.qt-project.org/151000
35
36class KRecursiveFilterProxyModelPrivate
37{
38 Q_DECLARE_PUBLIC(KRecursiveFilterProxyModel)
39 KRecursiveFilterProxyModel *q_ptr;
40public:
41 KRecursiveFilterProxyModelPrivate(KRecursiveFilterProxyModel *model)
42 : q_ptr(model),
43 completeInsert(false)
44 {
45 qRegisterMetaType<QModelIndex>("QModelIndex");
46 }
47
48 inline QMetaMethod findMethod(const char *signature) const
49 {
50 Q_Q(const KRecursiveFilterProxyModel);
51 const int idx = q->metaObject()->indexOfMethod(signature);
52 Q_ASSERT(idx != -1);
53 return q->metaObject()->method(idx);
54 }
55
56 // Convenience methods for invoking the QSFPM Q_SLOTS. Those slots must be invoked with invokeMethod
57 // because they are Q_PRIVATE_SLOTs
58 inline void invokeDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>())
59 {
60 Q_Q(KRecursiveFilterProxyModel);
61 // required for Qt 5.5 and upwards, see commit f96baeb75fc in qtbase
62 static const QMetaMethod m = findMethod("_q_sourceDataChanged(QModelIndex,QModelIndex,QVector<int>)");
63 bool success = m.invoke(q, Qt::DirectConnection,
64 Q_ARG(QModelIndex, topLeft),
65 Q_ARG(QModelIndex, bottomRight),
66 Q_ARG(QVector<int>, roles));
67 Q_UNUSED(success);
68 Q_ASSERT(success);
69 }
70
71 inline void invokeRowsInserted(const QModelIndex &source_parent, int start, int end)
72 {
73 Q_Q(KRecursiveFilterProxyModel);
74 static const QMetaMethod m = findMethod("_q_sourceRowsInserted(QModelIndex,int,int)");
75 bool success = m.invoke(q, Qt::DirectConnection,
76 Q_ARG(QModelIndex, source_parent),
77 Q_ARG(int, start),
78 Q_ARG(int, end));
79 Q_UNUSED(success);
80 Q_ASSERT(success);
81 }
82
83 inline void invokeRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
84 {
85 Q_Q(KRecursiveFilterProxyModel);
86 static const QMetaMethod m = findMethod("_q_sourceRowsAboutToBeInserted(QModelIndex,int,int)");
87 bool success = m.invoke(q, Qt::DirectConnection,
88 Q_ARG(QModelIndex, source_parent),
89 Q_ARG(int, start),
90 Q_ARG(int, end));
91 Q_UNUSED(success);
92 Q_ASSERT(success);
93 }
94
95 inline void invokeRowsRemoved(const QModelIndex &source_parent, int start, int end)
96 {
97 Q_Q(KRecursiveFilterProxyModel);
98 static const QMetaMethod m = findMethod("_q_sourceRowsRemoved(QModelIndex,int,int)");
99 bool success = m.invoke(q, Qt::DirectConnection,
100 Q_ARG(QModelIndex, source_parent),
101 Q_ARG(int, start),
102 Q_ARG(int, end));
103 Q_UNUSED(success);
104 Q_ASSERT(success);
105 }
106
107 inline void invokeRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
108 {
109 Q_Q(KRecursiveFilterProxyModel);
110 static const QMetaMethod m = findMethod("_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int)");
111 bool success = m.invoke(q, Qt::DirectConnection,
112 Q_ARG(QModelIndex, source_parent),
113 Q_ARG(int, start),
114 Q_ARG(int, end));
115 Q_UNUSED(success);
116 Q_ASSERT(success);
117 }
118
119 void sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right, const QVector<int> &roles = QVector<int>());
120 void sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end);
121 void sourceRowsInserted(const QModelIndex &source_parent, int start, int end);
122 void sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end);
123 void sourceRowsRemoved(const QModelIndex &source_parent, int start, int end);
124
125 /**
126 Force QSortFilterProxyModel to re-evaluate whether to hide or show index and its parents.
127 */
128 void refreshAscendantMapping(const QModelIndex &index);
129
130 QModelIndex lastFilteredOutAscendant(const QModelIndex &index);
131
132 bool completeInsert;
133 QModelIndex lastHiddenAscendantForInsert;
134};
135
136void KRecursiveFilterProxyModelPrivate::sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right, const QVector<int> &roles)
137{
138 QModelIndex source_parent = source_top_left.parent();
139 Q_ASSERT(source_bottom_right.parent() == source_parent); // don't know how to handle different parents in this code...
140
141 // Tell the world.
142 invokeDataChanged(source_top_left, source_bottom_right, roles);
143
144 // We can't find out if the change really matters to us or not, for a lack of a dataAboutToBeChanged signal (or a cache).
145 // TODO: add a set of roles that we care for, so we can at least ignore the rest.
146
147 // Even if we knew the visibility was just toggled, we also can't find out what
148 // was the last filtered out ascendant (on show, like sourceRowsAboutToBeInserted does)
149 // or the last to-be-filtered-out ascendant (on hide, like sourceRowsRemoved does)
150 // So we have to refresh all parents.
151 QModelIndex sourceParent = source_parent;
152 while (sourceParent.isValid()) {
153 invokeDataChanged(sourceParent, sourceParent, roles);
154 sourceParent = sourceParent.parent();
155 }
156}
157
158QModelIndex KRecursiveFilterProxyModelPrivate::lastFilteredOutAscendant(const QModelIndex &idx)
159{
160 Q_Q(KRecursiveFilterProxyModel);
161 QModelIndex last = idx;
162 QModelIndex index = idx.parent();
163 while (index.isValid() && !q->filterAcceptsRow(index.row(), index.parent())) {
164 last = index;
165 index = index.parent();
166 }
167 return last;
168}
169
170void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
171{
172 Q_Q(KRecursiveFilterProxyModel);
173
174 if (!source_parent.isValid() || q->filterAcceptsRow(source_parent.row(), source_parent.parent())) {
175 // If the parent is already in the model (directly or indirectly), we can just pass on the signal.
176 invokeRowsAboutToBeInserted(source_parent, start, end);
177 completeInsert = true;
178 } else {
179 // OK, so parent is not in the model.
180 // Maybe the grand parent neither.. Go up until the first one that is.
181 lastHiddenAscendantForInsert = lastFilteredOutAscendant(source_parent);
182 }
183}
184
185void KRecursiveFilterProxyModelPrivate::sourceRowsInserted(const QModelIndex &source_parent, int start, int end)
186{
187 Q_Q(KRecursiveFilterProxyModel);
188
189 if (completeInsert) {
190 // If the parent is already in the model, we can just pass on the signal.
191 completeInsert = false;
192 invokeRowsInserted(source_parent, start, end);
193 return;
194 }
195
196 bool requireRow = false;
197 for (int row = start; row <= end; ++row) {
198 if (q->filterAcceptsRow(row, source_parent)) {
199 requireRow = true;
200 break;
201 }
202 }
203
204 if (!requireRow) {
205 // The new rows doesn't have any descendants that match the filter. Filter them out.
206 return;
207 }
208
209 // Make QSFPM realize that lastHiddenAscendantForInsert should be shown now
210 invokeDataChanged(lastHiddenAscendantForInsert, lastHiddenAscendantForInsert);
211}
212
213void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
214{
215 invokeRowsAboutToBeRemoved(source_parent, start, end);
216}
217
218void KRecursiveFilterProxyModelPrivate::sourceRowsRemoved(const QModelIndex &source_parent, int start, int end)
219{
220 Q_Q(KRecursiveFilterProxyModel);
221
222 invokeRowsRemoved(source_parent, start, end);
223
224 // Find out if removing this visible row means that some ascendant
225 // row can now be hidden.
226 // We go up until we find a row that should still be visible
227 // and then make QSFPM re-evaluate the last one we saw before that, to hide it.
228
229 QModelIndex toHide;
230 QModelIndex sourceAscendant = source_parent;
231 while (sourceAscendant.isValid()) {
232 if (q->filterAcceptsRow(sourceAscendant.row(), sourceAscendant.parent())) {
233 break;
234 }
235 toHide = sourceAscendant;
236 sourceAscendant = sourceAscendant.parent();
237 }
238 if (toHide.isValid()) {
239 invokeDataChanged(toHide, toHide);
240 }
241}
242
243KRecursiveFilterProxyModel::KRecursiveFilterProxyModel(QObject *parent)
244 : QSortFilterProxyModel(parent), d_ptr(new KRecursiveFilterProxyModelPrivate(this))
245{
246 setDynamicSortFilter(true);
247}
248
249KRecursiveFilterProxyModel::~KRecursiveFilterProxyModel()
250{
251 delete d_ptr;
252}
253
254bool KRecursiveFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
255{
256 // TODO: Implement some caching so that if one match is found on the first pass, we can return early results
257 // when the subtrees are checked by QSFPM.
258 if (acceptRow(sourceRow, sourceParent)) {
259 return true;
260 }
261
262 QModelIndex source_index = sourceModel()->index(sourceRow, 0, sourceParent);
263 Q_ASSERT(source_index.isValid());
264 bool accepted = false;
265
266 const int numChildren = sourceModel()->rowCount(source_index);
267 for (int row = 0, rows = numChildren; row < rows; ++row) {
268 if (filterAcceptsRow(row, source_index)) {
269 accepted = true;
270 break;
271 }
272 }
273
274 return accepted;
275}
276
277QModelIndexList KRecursiveFilterProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
278{
279 if (role < Qt::UserRole) {
280 return QSortFilterProxyModel::match(start, role, value, hits, flags);
281 }
282
283 QModelIndexList list;
284 if (!sourceModel())
285 return list;
286
287 QModelIndex proxyIndex;
288 Q_FOREACH (const QModelIndex &idx, sourceModel()->match(mapToSource(start), role, value, hits, flags)) {
289 proxyIndex = mapFromSource(idx);
290 if (proxyIndex.isValid()) {
291 list << proxyIndex;
292 }
293 }
294
295 return list;
296}
297
298bool KRecursiveFilterProxyModel::acceptRow(int sourceRow, const QModelIndex &sourceParent) const
299{
300 return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
301}
302
303void KRecursiveFilterProxyModel::setSourceModel(QAbstractItemModel *model)
304{
305 // Standard disconnect of the previous source model, if present
306 if (sourceModel()) {
307 disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),
308 this, SLOT(sourceDataChanged(QModelIndex,QModelIndex,QVector<int>)));
309
310 disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
311 this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
312
313 disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
314 this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
315
316 disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
317 this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
318
319 disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
320 this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
321 }
322
323 QSortFilterProxyModel::setSourceModel(model);
324
325 // Disconnect in the QSortFilterProxyModel. These methods will be invoked manually
326 // in invokeDataChanged, invokeRowsInserted etc.
327 //
328 // The reason for that is that when the source model adds new rows for example, the new rows
329 // May not match the filter, but maybe their child items do match.
330 //
331 // Source model before insert:
332 //
333 // - A
334 // - B
335 // - - C
336 // - - D
337 // - - - E
338 // - - - F
339 // - - - G
340 // - H
341 // - I
342 //
343 // If the A F and L (which doesn't exist in the source model yet) match the filter
344 // the proxy will be:
345 //
346 // - A
347 // - B
348 // - - D
349 // - - - F
350 //
351 // New rows are inserted in the source model below H:
352 //
353 // - A
354 // - B
355 // - - C
356 // - - D
357 // - - - E
358 // - - - F
359 // - - - G
360 // - H
361 // - - J
362 // - - K
363 // - - - L
364 // - I
365 //
366 // As L matches the filter, it should be part of the KRecursiveFilterProxyModel.
367 //
368 // - A
369 // - B
370 // - - D
371 // - - - F
372 // - H
373 // - - K
374 // - - - L
375 //
376 // when the QSortFilterProxyModel gets a notification about new rows in H, it only checks
377 // J and K to see if they match, ignoring L, and therefore not adding it to the proxy.
378 // To work around that, we make sure that the QSFPM slot which handles that change in
379 // the source model (_q_sourceRowsAboutToBeInserted) does not get called directly.
380 // Instead we connect the sourceModel signal to our own slot in *this (sourceRowsAboutToBeInserted)
381 // Inside that method, the entire new subtree is queried (J, K *and* L) to see if there is a match,
382 // then the relevant Q_SLOTS in QSFPM are invoked.
383 // In the example above, we need to tell the QSFPM that H should be queried again to see if
384 // it matches the filter. It did not before, because L did not exist before. Now it does. That is
385 // achieved by telling the QSFPM that the data changed for H, which causes it to requery this class
386 // to see if H matches the filter (which it now does as L now exists).
387 // That is done in sourceRowsInserted.
388
389 if (!model) {
390 return;
391 }
392
393 disconnect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),
394 this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex,QVector<int>)));
395
396 disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
397 this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int)));
398
399 disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
400 this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int)));
401
402 disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
403 this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
404
405 disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
406 this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int)));
407
408 // Slots for manual invoking of QSortFilterProxyModel methods.
409 connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)),
410 this, SLOT(sourceDataChanged(QModelIndex,QModelIndex,QVector<int>)));
411
412 connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
413 this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
414
415 connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
416 this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
417
418 connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
419 this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
420
421 connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
422 this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
423
424}
425
426#include "moc_krecursivefilterproxymodel.cpp"
diff --git a/framework/src/krecursivefilterproxymodel.h b/framework/src/krecursivefilterproxymodel.h
new file mode 100644
index 00000000..d780b18c
--- /dev/null
+++ b/framework/src/krecursivefilterproxymodel.h
@@ -0,0 +1,135 @@
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#ifndef KRECURSIVEFILTERPROXYMODEL_H
21#define KRECURSIVEFILTERPROXYMODEL_H
22
23#include <QtCore/QSortFilterProxyModel>
24
25class KRecursiveFilterProxyModelPrivate;
26
27/**
28 @class KRecursiveFilterProxyModel krecursivefilterproxymodel.h KRecursiveFilterProxyModel
29
30 @brief Implements recursive filtering of models
31
32 Until Qt 5.10, QSortFilterProxyModel did not recurse when invoking a filtering stage, so that
33 if a particular row is filtered out, its children are not even checked to see if they match the filter.
34
35 If you can depend on Qt >= 5.10, then just use QSortFilterProxyModel::setRecursiveFiltering(true),
36 and you don't need to use KRecursiveFilterProxyModel.
37
38 For example, given a source model:
39
40 @verbatim
41 - A
42 - B
43 - - C
44 - - - D
45 - - - - E
46 - - - F
47 - - G
48 - - H
49 - I
50 @endverbatim
51
52 If a QSortFilterProxyModel is used with a filter matching A, D, G and I, the QSortFilterProxyModel will contain
53
54 @verbatim
55 - A
56 - I
57 @endverbatim
58
59 That is, even though D and E match the filter, they are not represented in the proxy model because B does not
60 match the filter and is filtered out.
61
62 The KRecursiveFilterProxyModel checks child indexes for filter matching and ensures that all matching indexes
63 are represented in the model.
64
65 In the above example, the KRecursiveFilterProxyModel will contain
66
67 @verbatim
68 - A
69 - B
70 - - C
71 - - - D
72 - - G
73 - I
74 @endverbatim
75
76 That is, the leaves in the model match the filter, but not necessarily the inner branches.
77
78 QSortFilterProxyModel provides the virtual method filterAcceptsRow to allow custom filter implementations.
79 Custom filter implementations can be written for KRecuriveFilterProxyModel using the acceptRow virtual method.
80
81 Note that using this proxy model is additional overhead compared to QSortFilterProxyModel as every index in the
82 model must be visited and queried.
83
84 @author Stephen Kelly <steveire@gmail.com>
85
86 @since 4.5
87
88*/
89class KRecursiveFilterProxyModel : public QSortFilterProxyModel
90{
91 Q_OBJECT
92public:
93 /**
94 Constructor
95 */
96 explicit KRecursiveFilterProxyModel(QObject *parent = nullptr);
97
98 /**
99 Destructor
100 */
101 virtual ~KRecursiveFilterProxyModel();
102
103 /** @reimp */
104 void setSourceModel(QAbstractItemModel *model) Q_DECL_OVERRIDE;
105
106 /**
107 * @reimplemented
108 */
109 QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits = 1,
110 Qt::MatchFlags flags = Qt::MatchFlags(Qt::MatchStartsWith | Qt::MatchWrap)) const Q_DECL_OVERRIDE;
111
112protected:
113 /**
114 Reimplement this method for custom filtering strategies.
115 */
116 virtual bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const;
117
118 /** @reimp */
119 bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_OVERRIDE;
120
121 KRecursiveFilterProxyModelPrivate *const d_ptr;
122
123private:
124 //@cond PRIVATE
125 Q_DECLARE_PRIVATE(KRecursiveFilterProxyModel)
126
127 Q_PRIVATE_SLOT(d_func(), void sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right, const QVector<int> &roles = QVector<int>()))
128 Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end))
129 Q_PRIVATE_SLOT(d_func(), void sourceRowsInserted(const QModelIndex &source_parent, int start, int end))
130 Q_PRIVATE_SLOT(d_func(), void sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end))
131 Q_PRIVATE_SLOT(d_func(), void sourceRowsRemoved(const QModelIndex &source_parent, int start, int end))
132 //@endcond
133};
134
135#endif