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 
29 public:
30  KRecursiveFilterProxyModelPrivate(KRecursiveFilterProxyModel *model)
31  : q_ptr(model)
32  , completeInsert(false)
33  {
34  qRegisterMetaType<QModelIndex>("QModelIndex");
35  }
36 
37  inline QMetaMethod findMethod(const char *signature) const
38  {
39  Q_Q(const KRecursiveFilterProxyModel);
40  const int idx = q->metaObject()->indexOfMethod(signature);
41  Q_ASSERT(idx != -1);
42  return q->metaObject()->method(idx);
43  }
44 
45  // Convenience methods for invoking the QSFPM Q_SLOTS. Those slots must be invoked with invokeMethod
46  // because they are Q_PRIVATE_SLOTs
47  inline void invokeDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>())
48  {
50  // required for Qt 5.5 and upwards, see commit f96baeb75fc in qtbase
51  static const QMetaMethod m = findMethod("_q_sourceDataChanged(QModelIndex,QModelIndex,QVector<int>)");
52  bool success = m.invoke(q, Qt::DirectConnection, Q_ARG(QModelIndex, topLeft), Q_ARG(QModelIndex, bottomRight), Q_ARG(QVector<int>, roles));
53  Q_UNUSED(success);
54  Q_ASSERT(success);
55  }
56 
57  inline void invokeRowsInserted(const QModelIndex &source_parent, int start, int end)
58  {
60  static const QMetaMethod m = findMethod("_q_sourceRowsInserted(QModelIndex,int,int)");
61  bool success = m.invoke(q, Qt::DirectConnection, Q_ARG(QModelIndex, source_parent), Q_ARG(int, start), Q_ARG(int, end));
62  Q_UNUSED(success);
63  Q_ASSERT(success);
64  }
65 
66  inline void invokeRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
67  {
69  static const QMetaMethod m = findMethod("_q_sourceRowsAboutToBeInserted(QModelIndex,int,int)");
70  bool success = m.invoke(q, Qt::DirectConnection, Q_ARG(QModelIndex, source_parent), Q_ARG(int, start), Q_ARG(int, end));
71  Q_UNUSED(success);
72  Q_ASSERT(success);
73  }
74 
75  inline void invokeRowsRemoved(const QModelIndex &source_parent, int start, int end)
76  {
78  static const QMetaMethod m = findMethod("_q_sourceRowsRemoved(QModelIndex,int,int)");
79  bool success = m.invoke(q, Qt::DirectConnection, Q_ARG(QModelIndex, source_parent), Q_ARG(int, start), Q_ARG(int, end));
80  Q_UNUSED(success);
81  Q_ASSERT(success);
82  }
83 
84  inline void invokeRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
85  {
87  static const QMetaMethod m = findMethod("_q_sourceRowsAboutToBeRemoved(QModelIndex,int,int)");
88  bool success = m.invoke(q, Qt::DirectConnection, Q_ARG(QModelIndex, source_parent), Q_ARG(int, start), Q_ARG(int, end));
89  Q_UNUSED(success);
90  Q_ASSERT(success);
91  }
92 
93  void sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right, const QVector<int> &roles = QVector<int>());
94  void sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end);
95  void sourceRowsInserted(const QModelIndex &source_parent, int start, int end);
96  void sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end);
97  void sourceRowsRemoved(const QModelIndex &source_parent, int start, int end);
98 
99  /**
100  Force QSortFilterProxyModel to re-evaluate whether to hide or show index and its parents.
101  */
102  void refreshAscendantMapping(const QModelIndex &index);
103 
104  QModelIndex lastFilteredOutAscendant(const QModelIndex &index);
105 
106  bool completeInsert;
107  QModelIndex lastHiddenAscendantForInsert;
108 };
109 
110 void KRecursiveFilterProxyModelPrivate::sourceDataChanged(const QModelIndex &source_top_left, const QModelIndex &source_bottom_right, const QVector<int> &roles)
111 {
112  QModelIndex source_parent = source_top_left.parent();
113  Q_ASSERT(source_bottom_right.parent() == source_parent); // don't know how to handle different parents in this code...
114 
115  // Tell the world.
116  invokeDataChanged(source_top_left, source_bottom_right, roles);
117 
118  // We can't find out if the change really matters to us or not, for a lack of a dataAboutToBeChanged signal (or a cache).
119  // TODO: add a set of roles that we care for, so we can at least ignore the rest.
120 
121  // Even if we knew the visibility was just toggled, we also can't find out what
122  // was the last filtered out ascendant (on show, like sourceRowsAboutToBeInserted does)
123  // or the last to-be-filtered-out ascendant (on hide, like sourceRowsRemoved does)
124  // So we have to refresh all parents.
125  QModelIndex sourceParent = source_parent;
126  while (sourceParent.isValid()) {
127  invokeDataChanged(sourceParent, sourceParent, roles);
128  sourceParent = sourceParent.parent();
129  }
130 }
131 
132 QModelIndex KRecursiveFilterProxyModelPrivate::lastFilteredOutAscendant(const QModelIndex &idx)
133 {
135  QModelIndex last = idx;
136  QModelIndex index = idx.parent();
137  while (index.isValid() && !q->filterAcceptsRow(index.row(), index.parent())) {
138  last = index;
139  index = index.parent();
140  }
141  return last;
142 }
143 
144 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeInserted(const QModelIndex &source_parent, int start, int end)
145 {
147 
148  if (!source_parent.isValid() || q->filterAcceptsRow(source_parent.row(), source_parent.parent())) {
149  // If the parent is already in the model (directly or indirectly), we can just pass on the signal.
150  invokeRowsAboutToBeInserted(source_parent, start, end);
151  completeInsert = true;
152  } else {
153  // OK, so parent is not in the model.
154  // Maybe the grand parent neither.. Go up until the first one that is.
155  lastHiddenAscendantForInsert = lastFilteredOutAscendant(source_parent);
156  }
157 }
158 
159 void KRecursiveFilterProxyModelPrivate::sourceRowsInserted(const QModelIndex &source_parent, int start, int end)
160 {
162 
163  if (completeInsert) {
164  // If the parent is already in the model, we can just pass on the signal.
165  completeInsert = false;
166  invokeRowsInserted(source_parent, start, end);
167  return;
168  }
169 
170  bool requireRow = false;
171  for (int row = start; row <= end; ++row) {
172  if (q->filterAcceptsRow(row, source_parent)) {
173  requireRow = true;
174  break;
175  }
176  }
177 
178  if (!requireRow) {
179  // The new rows doesn't have any descendants that match the filter. Filter them out.
180  return;
181  }
182 
183  // Make QSFPM realize that lastHiddenAscendantForInsert should be shown now
184  invokeDataChanged(lastHiddenAscendantForInsert, lastHiddenAscendantForInsert);
185 }
186 
187 void KRecursiveFilterProxyModelPrivate::sourceRowsAboutToBeRemoved(const QModelIndex &source_parent, int start, int end)
188 {
189  invokeRowsAboutToBeRemoved(source_parent, start, end);
190 }
191 
192 void KRecursiveFilterProxyModelPrivate::sourceRowsRemoved(const QModelIndex &source_parent, int start, int end)
193 {
195 
196  invokeRowsRemoved(source_parent, start, end);
197 
198  // Find out if removing this visible row means that some ascendant
199  // row can now be hidden.
200  // We go up until we find a row that should still be visible
201  // and then make QSFPM re-evaluate the last one we saw before that, to hide it.
202 
203  QModelIndex toHide;
204  QModelIndex sourceAscendant = source_parent;
205  while (sourceAscendant.isValid()) {
206  if (q->filterAcceptsRow(sourceAscendant.row(), sourceAscendant.parent())) {
207  break;
208  }
209  toHide = sourceAscendant;
210  sourceAscendant = sourceAscendant.parent();
211  }
212  if (toHide.isValid()) {
213  invokeDataChanged(toHide, toHide);
214  }
215 }
216 
218  : QSortFilterProxyModel(parent)
219  , d_ptr(new KRecursiveFilterProxyModelPrivate(this))
220 {
221  setDynamicSortFilter(true);
222 }
223 
225 
226 bool KRecursiveFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
227 {
228  // TODO: Implement some caching so that if one match is found on the first pass, we can return early results
229  // when the subtrees are checked by QSFPM.
230  if (acceptRow(sourceRow, sourceParent)) {
231  return true;
232  }
233 
234  QModelIndex source_index = sourceModel()->index(sourceRow, 0, sourceParent);
235  Q_ASSERT(source_index.isValid());
236  bool accepted = false;
237 
238  const int numChildren = sourceModel()->rowCount(source_index);
239  for (int row = 0, rows = numChildren; row < rows; ++row) {
240  if (filterAcceptsRow(row, source_index)) {
241  accepted = true;
242  break;
243  }
244  }
245 
246  return accepted;
247 }
248 
249 QModelIndexList KRecursiveFilterProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const
250 {
251  if (role < Qt::UserRole) {
252  return QSortFilterProxyModel::match(start, role, value, hits, flags);
253  }
254 
255  QModelIndexList list;
256  if (!sourceModel()) {
257  return list;
258  }
259 
260  QModelIndex proxyIndex;
261  const auto lst = sourceModel()->match(mapToSource(start), role, value, hits, flags);
262  for (const QModelIndex &idx : lst) {
263  proxyIndex = mapFromSource(idx);
264  if (proxyIndex.isValid()) {
265  list << proxyIndex;
266  }
267  }
268 
269  return list;
270 }
271 
272 bool KRecursiveFilterProxyModel::acceptRow(int sourceRow, const QModelIndex &sourceParent) const
273 {
274  return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
275 }
276 
278 {
279  // Standard disconnect of the previous source model, if present
280  if (sourceModel()) {
283  this,
284  SLOT(sourceDataChanged(QModelIndex, QModelIndex, QVector<int>)));
285 
286  disconnect(sourceModel(), SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(sourceRowsAboutToBeInserted(QModelIndex, int, int)));
287 
288  disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(sourceRowsInserted(QModelIndex, int, int)));
289 
290  disconnect(sourceModel(), SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex, int, int)));
291 
292  disconnect(sourceModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(sourceRowsRemoved(QModelIndex, int, int)));
293  }
294 
296 
297  // Disconnect in the QSortFilterProxyModel. These methods will be invoked manually
298  // in invokeDataChanged, invokeRowsInserted etc.
299  //
300  // The reason for that is that when the source model adds new rows for example, the new rows
301  // May not match the filter, but maybe their child items do match.
302  //
303  // Source model before insert:
304  //
305  // - A
306  // - B
307  // - - C
308  // - - D
309  // - - - E
310  // - - - F
311  // - - - G
312  // - H
313  // - I
314  //
315  // If the A F and L (which doesn't exist in the source model yet) match the filter
316  // the proxy will be:
317  //
318  // - A
319  // - B
320  // - - D
321  // - - - F
322  //
323  // New rows are inserted in the source model below H:
324  //
325  // - A
326  // - B
327  // - - C
328  // - - D
329  // - - - E
330  // - - - F
331  // - - - G
332  // - H
333  // - - J
334  // - - K
335  // - - - L
336  // - I
337  //
338  // As L matches the filter, it should be part of the KRecursiveFilterProxyModel.
339  //
340  // - A
341  // - B
342  // - - D
343  // - - - F
344  // - H
345  // - - K
346  // - - - L
347  //
348  // when the QSortFilterProxyModel gets a notification about new rows in H, it only checks
349  // J and K to see if they match, ignoring L, and therefore not adding it to the proxy.
350  // To work around that, we make sure that the QSFPM slot which handles that change in
351  // the source model (_q_sourceRowsAboutToBeInserted) does not get called directly.
352  // Instead we connect the sourceModel signal to our own slot in *this (sourceRowsAboutToBeInserted)
353  // Inside that method, the entire new subtree is queried (J, K *and* L) to see if there is a match,
354  // then the relevant Q_SLOTS in QSFPM are invoked.
355  // In the example above, we need to tell the QSFPM that H should be queried again to see if
356  // it matches the filter. It did not before, because L did not exist before. Now it does. That is
357  // achieved by telling the QSFPM that the data changed for H, which causes it to requery this class
358  // to see if H matches the filter (which it now does as L now exists).
359  // That is done in sourceRowsInserted.
360 
361  if (!model) {
362  return;
363  }
364 
365  disconnect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector<int>)), this, SLOT(_q_sourceDataChanged(QModelIndex, QModelIndex, QVector<int>)));
366 
367  disconnect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(_q_sourceRowsAboutToBeInserted(QModelIndex, int, int)));
368 
369  disconnect(model, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(_q_sourceRowsInserted(QModelIndex, int, int)));
370 
371  disconnect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(_q_sourceRowsAboutToBeRemoved(QModelIndex, int, int)));
372 
373  disconnect(model, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(_q_sourceRowsRemoved(QModelIndex, int, int)));
374 
375  // Slots for manual invoking of QSortFilterProxyModel methods.
376  connect(model, SIGNAL(dataChanged(QModelIndex, QModelIndex, QVector<int>)), this, SLOT(sourceDataChanged(QModelIndex, QModelIndex, QVector<int>)));
377 
378  connect(model, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(sourceRowsAboutToBeInserted(QModelIndex, int, int)));
379 
380  connect(model, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(sourceRowsInserted(QModelIndex, int, int)));
381 
382  connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(sourceRowsAboutToBeRemoved(QModelIndex, int, int)));
383 
384  connect(model, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(sourceRowsRemoved(QModelIndex, int, int)));
385 }
386 
387 #include "moc_krecursivefilterproxymodel.cpp"
388 #endif
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
UserRole
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
virtual bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const
Reimplement this method for custom filtering strategies.
void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
Q_SCRIPTABLE Q_NOREPLY void start()
void setSourceModel(QAbstractItemModel *model) override
virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const const override
virtual void setSourceModel(QAbstractItemModel *sourceModel) override
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
virtual Qt::ItemFlags flags(const QModelIndex &index) const const override
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
Implements recursive filtering of models.
DirectConnection
virtual QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const const override
void setDynamicSortFilter(bool enable)
~KRecursiveFilterProxyModel() override
Destructor.
bool isValid() const const
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
int row() const const
void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
KRecursiveFilterProxyModel(QObject *parent=nullptr)
Constructor.
void rowsInserted(const QModelIndex &parent, int first, int last)
void rowsRemoved(const QModelIndex &parent, int first, int last)
virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const const override
QModelIndex parent() const const
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const const
typedef MatchFlags
QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits=1, Qt::MatchFlags flags=Qt::MatchFlags(Qt::MatchStartsWith|Qt::MatchWrap)) const override
@reimplemented
const QList< QKeySequence > & end()
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Wed Sep 28 2022 04:02:54 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.