diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2017-08-20 13:55:56 -0600 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2017-08-20 13:55:56 -0600 |
commit | f6e6cd8a5c8ef2d8d7e331834dff026646be543e (patch) | |
tree | 68fda89590301378d1e2658498451a1c43f55aea | |
parent | c6d028cebbfce9e4d9437ace37a5ac327eaee25d (diff) | |
download | kube-f6e6cd8a5c8ef2d8d7e331834dff026646be543e.tar.gz kube-f6e6cd8a5c8ef2d8d7e331834dff026646be543e.zip |
Filter enabled folders using a krecursivefilterproxymodel
The KRecursiveFilterProxyModel is necessary until Qt 5.10 when
QSortFilterProxyModel will support recursive filtering.
For the recursive filtering to work we need to make all data available,
so we trigger fetchMore on all added indexes.
-rw-r--r-- | framework/src/CMakeLists.txt | 1 | ||||
-rw-r--r-- | framework/src/domain/folderlistmodel.cpp | 20 | ||||
-rw-r--r-- | framework/src/domain/folderlistmodel.h | 7 | ||||
-rw-r--r-- | framework/src/krecursivefilterproxymodel.cpp | 426 | ||||
-rw-r--r-- | framework/src/krecursivefilterproxymodel.h | 135 |
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 | ||
47 | add_library(frameworkplugin SHARED ${SRCS}) | 48 | add_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 @@ | |||
26 | using namespace Sink; | 26 | using namespace Sink; |
27 | using namespace Sink::ApplicationDomain; | 27 | using namespace Sink::ApplicationDomain; |
28 | 28 | ||
29 | FolderListModel::FolderListModel(QObject *parent) : QSortFilterProxyModel() | 29 | FolderListModel::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 | ||
36 | FolderListModel::~FolderListModel() | 45 | FolderListModel::~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 | ||
155 | bool 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 | |||
147 | void FolderListModel::setFolderId(const QVariant &folderId) | 163 | void 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 | ||
28 | namespace Sink { | 26 | namespace Sink { |
29 | class Query; | 27 | class Query; |
30 | } | 28 | } |
31 | 29 | ||
32 | class FolderListModel : public QSortFilterProxyModel | 30 | class 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; |
70 | protected: | 68 | protected: |
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 | ||
73 | private: | 72 | private: |
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 | |||
36 | class KRecursiveFilterProxyModelPrivate | ||
37 | { | ||
38 | Q_DECLARE_PUBLIC(KRecursiveFilterProxyModel) | ||
39 | KRecursiveFilterProxyModel *q_ptr; | ||
40 | public: | ||
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 | |||
136 | void 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 | |||
158 | QModelIndex 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 | |||
170 | void 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 | |||
185 | void 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 | |||
213 | void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end) | ||
214 | { | ||
215 | invokeRowsAboutToBeRemoved(source_parent, start, end); | ||
216 | } | ||
217 | |||
218 | void 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 | |||
243 | KRecursiveFilterProxyModel::KRecursiveFilterProxyModel(QObject *parent) | ||
244 | : QSortFilterProxyModel(parent), d_ptr(new KRecursiveFilterProxyModelPrivate(this)) | ||
245 | { | ||
246 | setDynamicSortFilter(true); | ||
247 | } | ||
248 | |||
249 | KRecursiveFilterProxyModel::~KRecursiveFilterProxyModel() | ||
250 | { | ||
251 | delete d_ptr; | ||
252 | } | ||
253 | |||
254 | bool 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 | |||
277 | QModelIndexList 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 | |||
298 | bool KRecursiveFilterProxyModel::acceptRow(int sourceRow, const QModelIndex &sourceParent) const | ||
299 | { | ||
300 | return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); | ||
301 | } | ||
302 | |||
303 | void 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 | |||
25 | class 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 | */ | ||
89 | class KRecursiveFilterProxyModel : public QSortFilterProxyModel | ||
90 | { | ||
91 | Q_OBJECT | ||
92 | public: | ||
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 | |||
112 | protected: | ||
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 | |||
123 | private: | ||
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 | ||