KItemModels

krecursivefilterproxymodel.cpp
1 /*
2  SPDX-FileCopyrightText: 2009 Stephen Kelly <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "krecursivefilterproxymodel.h"
8 
9 #if KITEMMODELS_BUILD_DEPRECATED_SINCE(5, 65)
10 #include <QMetaMethod>
11 
12 // Maintainability note:
13 // This class invokes some Q_PRIVATE_SLOTs in QSortFilterProxyModel which are
14 // private API and could be renamed or removed at any time.
15 // If they are renamed, the invocations can be updated with an #if (QT_VERSION(...))
16 // If they are removed, then layout{AboutToBe}Changed Q_SIGNALS should be used when the source model
17 // gets new rows or has rowsremoved or moved. The Q_PRIVATE_SLOT invocation is an optimization
18 // because layout{AboutToBe}Changed is expensive and causes the entire mapping of the tree in QSFPM
19 // to be cleared, even if only a part of it is dirty.
20 // Stephen Kelly, 30 April 2010.
21 
22 // All this is temporary anyway, the long term solution is support in QSFPM: https://codereview.qt-project.org/151000
23 
24 class KRecursiveFilterProxyModelPrivate
25 {
26  Q_DECLARE_PUBLIC(KRecursiveFilterProxyModel)
28 public:
29  KRecursiveFilterProxyModelPrivate(KRecursiveFilterProxyModel *model)
30  : q_ptr(model),
31  completeInsert(false)
32  {
33  qRegisterMetaType<QModelIndex>("QModelIndex");
34  }
35 
36  inline QMetaMethod findMethod(const char *signature) const
37  {
38  Q_Q(const KRecursiveFilterProxyModel);
39  const int idx = q->metaObject()->indexOfMethod(signature);
40  Q_ASSERT(idx != -1);
41  return q->metaObject()->method(idx);
42  }
43 
44  // Convenience methods for invoking the QSFPM Q_SLOTS. Those slots must be invoked with invokeMethod
45  // because they are Q_PRIVATE_SLOTs
46  inline void invokeDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>())
47  {
49  // required for Qt 5.5 and upwards, see commit f96baeb75fc in qtbase
50  static const QMetaMethod m = findMethod("_q_sourceDataChanged(QModelIndex,QModelIndex,QVector<int>)");
51  bool success = m.invoke(q, Qt::DirectConnection,
52  Q_ARG(QModelIndex, topLeft),
53  Q_ARG(QModelIndex, bottomRight),
54  Q_ARG(QVector<int>, roles));
55  Q_UNUSED(success);
56  Q_ASSERT(success);
57  }
58 
59  inline void invokeRowsInserted(const QModelIndex &source_parent, int start, int end)
60  {
62  static const QMetaMethod m = findMethod("_q_sourceRowsInserted(QModelIndex,int,int)");
63  bool success = m.invoke(q, 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  {
74  static const QMetaMethod m = findMethod("_q_sourceRowsAboutToBeInserted(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 invokeRowsRemoved(const QModelIndex &source_parent, int start, int end)
84  {
86  static const QMetaMethod m = findMethod("_q_sourceRowsRemoved(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 invokeRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
96  {
98  static const QMetaMethod m = findMethod("_q_sourceRowsAboutToBeRemoved(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  void sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right, const QVector<int> &roles = QVector<int>());
108  void sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end);
109  void sourceRowsInserted(const QModelIndex &source_parent, int start, int end);
110  void sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end);
111  void sourceRowsRemoved(const QModelIndex &source_parent, int start, int end);
112 
116  void refreshAscendantMapping(const QModelIndex &index);
117 
118  QModelIndex lastFilteredOutAscendant(const QModelIndex &index);
119 
120  bool completeInsert;
121  QModelIndex lastHiddenAscendantForInsert;
122 };
123 
124 void KRecursiveFilterProxyModelPrivate::sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right, const QVector<int> &roles)
125 {
126  QModelIndex source_parent = source_top_left.parent();
127  Q_ASSERT(source_bottom_right.parent() == source_parent); // don't know how to handle different parents in this code...
128 
129  // Tell the world.
130  invokeDataChanged(source_top_left, source_bottom_right, roles);
131 
132  // We can't find out if the change really matters to us or not, for a lack of a dataAboutToBeChanged signal (or a cache).
133  // TODO: add a set of roles that we care for, so we can at least ignore the rest.
134 
135  // Even if we knew the visibility was just toggled, we also can't find out what
136  // was the last filtered out ascendant (on show, like sourceRowsAboutToBeInserted does)
137  // or the last to-be-filtered-out ascendant (on hide, like sourceRowsRemoved does)
138  // So we have to refresh all parents.
139  QModelIndex sourceParent = source_parent;
140  while (sourceParent.isValid()) {
141  invokeDataChanged(sourceParent, sourceParent, roles);
142  sourceParent = sourceParent.parent();
143  }
144 }
145 
146 QModelIndex KRecursiveFilterProxyModelPrivate::lastFilteredOutAscendant(const QModelIndex &idx)
147 {
149  QModelIndex last = idx;
150  QModelIndex index = idx.parent();
151  while (index.isValid() && !q->filterAcceptsRow(index.row(), index.parent())) {
152  last = index;
153  index = index.parent();
154  }
155  return last;
156 }
157 
158 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
159 {
161 
162  if (!source_parent.isValid() || q->filterAcceptsRow(source_parent.row(), source_parent.parent())) {
163  // If the parent is already in the model (directly or indirectly), we can just pass on the signal.
164  invokeRowsAboutToBeInserted(source_parent, start, end);
165  completeInsert = true;
166  } else {
167  // OK, so parent is not in the model.
168  // Maybe the grand parent neither.. Go up until the first one that is.
169  lastHiddenAscendantForInsert = lastFilteredOutAscendant(source_parent);
170  }
171 }
172 
173 void KRecursiveFilterProxyModelPrivate::sourceRowsInserted(const QModelIndex &source_parent, int start, int end)
174 {
176 
177  if (completeInsert) {
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  if (q->filterAcceptsRow(row, source_parent)) {
187  requireRow = true;
188  break;
189  }
190  }
191 
192  if (!requireRow) {
193  // The new rows doesn't have any descendants that match the filter. Filter them out.
194  return;
195  }
196 
197  // Make QSFPM realize that lastHiddenAscendantForInsert should be shown now
198  invokeDataChanged(lastHiddenAscendantForInsert, lastHiddenAscendantForInsert);
199 }
200 
201 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
202 {
203  invokeRowsAboutToBeRemoved(source_parent, start, end);
204 }
205 
206 void KRecursiveFilterProxyModelPrivate::sourceRowsRemoved(const QModelIndex &source_parent, int start, int end)
207 {
209 
210  invokeRowsRemoved(source_parent, start, end);
211 
212  // Find out if removing this visible row means that some ascendant
213  // row can now be hidden.
214  // We go up until we find a row that should still be visible
215  // and then make QSFPM re-evaluate the last one we saw before that, to hide it.
216 
217  QModelIndex toHide;
218  QModelIndex sourceAscendant = source_parent;
219  while (sourceAscendant.isValid()) {
220  if (q->filterAcceptsRow(sourceAscendant.row(), sourceAscendant.parent())) {
221  break;
222  }
223  toHide = sourceAscendant;
224  sourceAscendant = sourceAscendant.parent();
225  }
226  if (toHide.isValid()) {
227  invokeDataChanged(toHide, toHide);
228  }
229 }
230 
232  : QSortFilterProxyModel(parent), d_ptr(new KRecursiveFilterProxyModelPrivate(this))
233 {
234  setDynamicSortFilter(true);
235 }
236 
238 {
239  delete d_ptr;
240 }
241 
242 bool KRecursiveFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
243 {
244  // TODO: Implement some caching so that if one match is found on the first pass, we can return early results
245  // when the subtrees are checked by QSFPM.
246  if (acceptRow(sourceRow, sourceParent)) {
247  return true;
248  }
249 
250  QModelIndex source_index = sourceModel()->index(sourceRow, 0, sourceParent);
251  Q_ASSERT(source_index.isValid());
252  bool accepted = false;
253 
254  const int numChildren = sourceModel()->rowCount(source_index);
255  for (int row = 0, rows = numChildren; row < rows; ++row) {
256  if (filterAcceptsRow(row, source_index)) {
257  accepted = true;
258  break;
259  }
260  }
261 
262  return accepted;
263 }
264 
265 QModelIndexList KRecursiveFilterProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
266 {
267  if (role < Qt::UserRole) {
268  return QSortFilterProxyModel::match(start, role, value, hits, flags);
269  }
270 
271  QModelIndexList list;
272  if (!sourceModel())
273  return list;
274 
275  QModelIndex proxyIndex;
276  const auto lst = sourceModel()->match(mapToSource(start), role, value, hits, flags);
277  for (const QModelIndex &idx : lst) {
278  proxyIndex = mapFromSource(idx);
279  if (proxyIndex.isValid()) {
280  list << proxyIndex;
281  }
282  }
283 
284  return list;
285 }
286 
287 bool KRecursiveFilterProxyModel::acceptRow(int sourceRow, const QModelIndex &sourceParent) const
288 {
289  return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
290 }
291 
293 {
294  // Standard disconnect of the previous source model, if present
295  if (sourceModel()) {
297  this, SLOT(sourceDataChanged(QModelIndex,QModelIndex,QVector<int>)));
298 
300  this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
301 
302  disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
303  this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
304 
306  this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
307 
308  disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
309  this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
310  }
311 
313 
314  // Disconnect in the QSortFilterProxyModel. These methods will be invoked manually
315  // in invokeDataChanged, invokeRowsInserted etc.
316  //
317  // The reason for that is that when the source model adds new rows for example, the new rows
318  // May not match the filter, but maybe their child items do match.
319  //
320  // Source model before insert:
321  //
322  // - A
323  // - B
324  // - - C
325  // - - D
326  // - - - E
327  // - - - F
328  // - - - G
329  // - H
330  // - I
331  //
332  // If the A F and L (which doesn't exist in the source model yet) match the filter
333  // the proxy will be:
334  //
335  // - A
336  // - B
337  // - - D
338  // - - - F
339  //
340  // New rows are inserted in the source model below H:
341  //
342  // - A
343  // - B
344  // - - C
345  // - - D
346  // - - - E
347  // - - - F
348  // - - - G
349  // - H
350  // - - J
351  // - - K
352  // - - - L
353  // - I
354  //
355  // As L matches the filter, it should be part of the KRecursiveFilterProxyModel.
356  //
357  // - A
358  // - B
359  // - - D
360  // - - - F
361  // - H
362  // - - K
363  // - - - L
364  //
365  // when the QSortFilterProxyModel gets a notification about new rows in H, it only checks
366  // J and K to see if they match, ignoring L, and therefore not adding it to the proxy.
367  // To work around that, we make sure that the QSFPM slot which handles that change in
368  // the source model (_q_sourceRowsAboutToBeInserted) does not get called directly.
369  // Instead we connect the sourceModel signal to our own slot in *this (sourceRowsAboutToBeInserted)
370  // Inside that method, the entire new subtree is queried (J, K *and* L) to see if there is a match,
371  // then the relevant Q_SLOTS in QSFPM are invoked.
372  // In the example above, we need to tell the QSFPM that H should be queried again to see if
373  // it matches the filter. It did not before, because L did not exist before. Now it does. That is
374  // achieved by telling the QSFPM that the data changed for H, which causes it to requery this class
375  // to see if H matches the filter (which it now does as L now exists).
376  // That is done in sourceRowsInserted.
377 
378  if (!model) {
379  return;
380  }
381 
383  this, SLOT(_q_sourceDataChanged(QModelIndex,QModelIndex,QVector<int>)));
384 
385  disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
386  this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex,int,int)));
387 
388  disconnect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
389  this, SLOT(_q_sourceRowsInserted(QModelIndex,int,int)));
390 
391  disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
392  this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
393 
394  disconnect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
395  this, SLOT(_q_sourceRowsRemoved(QModelIndex,int,int)));
396 
397  // Slots for manual invoking of QSortFilterProxyModel methods.
399  this, SLOT(sourceDataChanged(QModelIndex,QModelIndex,QVector<int>)));
400 
401  connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
402  this, SLOT(sourceRowsAboutToBeInserted(QModelIndex,int,int)));
403 
404  connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
405  this, SLOT(sourceRowsInserted(QModelIndex,int,int)));
406 
407  connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
408  this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex,int,int)));
409 
410  connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
411  this, SLOT(sourceRowsRemoved(QModelIndex,int,int)));
412 
413 }
414 
415 #include "moc_krecursivefilterproxymodel.cpp"
416 #endif
virtual int rowCount(const QModelIndex &parent) const const =0
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const =0
virtual Qt::ItemFlags flags(const QModelIndex &index) const const override
typedef MatchFlags
bool invoke(QObject *object, Qt::ConnectionType connectionType, QGenericReturnArgument returnValue, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9) const const
virtual bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const
Reimplement this method for custom filtering strategies.
virtual void setSourceModel(QAbstractItemModel *sourceModel) override
virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const const override
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const const
bool isValid() const const
void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
UserRole
int row() const const
void setDynamicSortFilter(bool enable)
~KRecursiveFilterProxyModel() override
Destructor.
KRecursiveFilterProxyModel(QObject *parent=nullptr)
Constructor.
QModelIndex parent() const const
void rowsRemoved(const QModelIndex &parent, int first, int last)
Implements recursive filtering of models.
void setSourceModel(QAbstractItemModel *model) override
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const const
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
QAbstractItemModel * sourceModel() const const
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const const override
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits=1, Qt::MatchFlags flags=Qt::MatchFlags(Qt::MatchStartsWith|Qt::MatchWrap)) const override
void rowsInserted(const QModelIndex &parent, int first, int last)
DirectConnection
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Sep 17 2020 22:49:19 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.