/ qqmlsortfilterproxymodel.cpp
qqmlsortfilterproxymodel.cpp
  1  #include "qqmlsortfilterproxymodel.h"
  2  #include <QtQml>
  3  #include <algorithm>
  4  #include "filters/filter.h"
  5  #include "sorters/sorter.h"
  6  #include "proxyroles/proxyrole.h"
  7  
  8  namespace qqsfpm {
  9  
 10  /*!
 11      \qmltype SortFilterProxyModel
 12      \inqmlmodule SortFilterProxyModel
 13      \ingroup SortFilterProxyModel
 14      \ingroup FilterContainer
 15      \ingroup SorterContainer
 16      \brief Filters and sorts data coming from a source \l {http://doc.qt.io/qt-5/qabstractitemmodel.html} {QAbstractItemModel}.
 17  
 18      The SortFilterProxyModel type provides support for filtering and sorting data coming from a source model.
 19      \sa FilterContainer, SorterContainer
 20  */
 21  QQmlSortFilterProxyModel::QQmlSortFilterProxyModel(QObject *parent) :
 22      QSortFilterProxyModel(parent),
 23  #ifdef SFPM_DELAYED
 24      m_delayed(true)
 25  #else
 26      m_delayed(false)
 27  #endif
 28  {
 29      connect(this, &QAbstractProxyModel::sourceModelChanged, this, &QQmlSortFilterProxyModel::updateRoles);
 30      connect(this, &QAbstractItemModel::modelReset, this, &QQmlSortFilterProxyModel::updateRoles);
 31      connect(this, &QAbstractItemModel::rowsInserted, this, &QQmlSortFilterProxyModel::countChanged);
 32      connect(this, &QAbstractItemModel::rowsRemoved, this, &QQmlSortFilterProxyModel::countChanged);
 33      connect(this, &QAbstractItemModel::modelReset, this, &QQmlSortFilterProxyModel::countChanged);
 34      connect(this, &QAbstractItemModel::layoutChanged, this, &QQmlSortFilterProxyModel::countChanged);
 35      connect(this, &QAbstractItemModel::dataChanged, this, &QQmlSortFilterProxyModel::onDataChanged);
 36      setDynamicSortFilter(true);
 37  }
 38  
 39  /*!
 40      \qmlproperty QAbstractItemModel* SortFilterProxyModel::sourceModel
 41  
 42      The source model of this proxy model
 43  */
 44  
 45  /*!
 46      \qmlproperty int SortFilterProxyModel::count
 47  
 48      The number of rows in the proxy model (not filtered out the source model)
 49  */
 50  int QQmlSortFilterProxyModel::count() const
 51  {
 52      return rowCount();
 53  }
 54  
 55  /*!
 56      \qmlproperty bool SortFilterProxyModel::delayed
 57  
 58      Delay the execution of filters, sorters and proxyRoles until the next event loop.
 59      This can be used as an optimization when multiple filters, sorters or proxyRoles are changed in a single event loop.
 60      They will be executed once in a single batch at the next event loop instead of being executed in multiple sequential batches.
 61  
 62      By default, the SortFilterProxyModel is not delayed, unless the \c SFPM_DELAYED environment variable is defined at compile time.
 63  */
 64  bool QQmlSortFilterProxyModel::delayed() const
 65  {
 66      return m_delayed;
 67  }
 68  
 69  void QQmlSortFilterProxyModel::setDelayed(bool delayed)
 70  {
 71      if (m_delayed == delayed)
 72          return;
 73  
 74      m_delayed = delayed;
 75      Q_EMIT delayedChanged();
 76  }
 77  
 78  const QString& QQmlSortFilterProxyModel::filterRoleName() const
 79  {
 80      return m_filterRoleName;
 81  }
 82  
 83  void QQmlSortFilterProxyModel::setFilterRoleName(const QString& filterRoleName)
 84  {
 85      if (m_filterRoleName == filterRoleName)
 86          return;
 87  
 88      m_filterRoleName = filterRoleName;
 89      updateFilterRole();
 90      Q_EMIT filterRoleNameChanged();
 91  }
 92  
 93  QString QQmlSortFilterProxyModel::filterPattern() const
 94  {
 95      return filterRegularExpression().pattern();
 96  }
 97  
 98  void QQmlSortFilterProxyModel::setFilterPattern(const QString& filterPattern)
 99  {
100      QRegularExpression regExp = filterRegularExpression();
101      if (regExp.pattern() == filterPattern)
102          return;
103  
104      regExp.setPattern(filterPattern);
105      QSortFilterProxyModel::setFilterRegularExpression(regExp);
106      Q_EMIT filterPatternChanged();
107  }
108  
109  const QVariant& QQmlSortFilterProxyModel::filterValue() const
110  {
111      return m_filterValue;
112  }
113  
114  void QQmlSortFilterProxyModel::setFilterValue(const QVariant& filterValue)
115  {
116      if (m_filterValue == filterValue)
117          return;
118  
119      m_filterValue = filterValue;
120      queueInvalidateFilter();
121      Q_EMIT filterValueChanged();
122  }
123  
124  /*!
125      \qmlproperty string SortFilterProxyModel::sortRoleName
126  
127      The role name of the source model's data used for the sorting.
128  
129      \sa {http://doc.qt.io/qt-5/qsortfilterproxymodel.html#sortRole-prop} {sortRole}, roleForName
130  */
131  const QString& QQmlSortFilterProxyModel::sortRoleName() const
132  {
133      return m_sortRoleName;
134  }
135  
136  void QQmlSortFilterProxyModel::setSortRoleName(const QString& sortRoleName)
137  {
138      if (m_sortRoleName == sortRoleName)
139          return;
140  
141      m_sortRoleName = sortRoleName;
142      updateSortRole();
143      Q_EMIT sortRoleNameChanged();
144  }
145  
146  bool QQmlSortFilterProxyModel::ascendingSortOrder() const
147  {
148      return m_ascendingSortOrder;
149  }
150  
151  void QQmlSortFilterProxyModel::setAscendingSortOrder(bool ascendingSortOrder)
152  {
153      if (m_ascendingSortOrder == ascendingSortOrder)
154          return;
155  
156      m_ascendingSortOrder = ascendingSortOrder;
157      Q_EMIT ascendingSortOrderChanged();
158      queueInvalidate();
159  }
160  
161  /*!
162      \qmlproperty list<Filter> SortFilterProxyModel::filters
163  
164      This property holds the list of filters for this proxy model. To be included in the model, a row of the source model has to be accepted by all the top level filters of this list.
165  
166      \sa Filter, FilterContainer
167  */
168  
169  /*!
170      \qmlproperty list<Sorter> SortFilterProxyModel::sorters
171  
172      This property holds the list of sorters for this proxy model. The rows of the source model are sorted by the sorters of this list, in their order of insertion.
173  
174      \sa Sorter, SorterContainer
175  */
176  
177  /*!
178      \qmlproperty list<ProxyRole> SortFilterProxyModel::proxyRoles
179  
180      This property holds the list of proxy roles for this proxy model. Each proxy role adds a new custom role to the model.
181  
182      \sa ProxyRole
183  */
184  
185  void QQmlSortFilterProxyModel::classBegin()
186  {
187  
188  }
189  
190  void QQmlSortFilterProxyModel::componentComplete()
191  {
192      m_completed = true;
193  
194      for (const auto& filter : qAsConst(m_filters))
195          filter->proxyModelCompleted(*this);
196      for (const auto& sorter : qAsConst(m_sorters))
197          sorter->proxyModelCompleted(*this);
198      for (const auto& proxyRole : qAsConst(m_proxyRoles))
199          proxyRole->proxyModelCompleted(*this);
200  
201      invalidate();
202      sort(0);
203  }
204  
205  QVariant QQmlSortFilterProxyModel::sourceData(const QModelIndex& sourceIndex, const QString& roleName) const
206  {
207      int role = roleNames().key(roleName.toUtf8());
208      return sourceData(sourceIndex, role);
209  }
210  
211  QVariant QQmlSortFilterProxyModel::sourceData(const QModelIndex &sourceIndex, int role) const
212  {
213      QPair<ProxyRole*, QString> proxyRolePair = m_proxyRoleMap[role];
214      if (ProxyRole* proxyRole = proxyRolePair.first)
215          return proxyRole->roleData(sourceIndex, *this, proxyRolePair.second);
216  
217      return sourceModel()->data(sourceIndex, role);
218  }
219  
220  QVariant QQmlSortFilterProxyModel::data(const QModelIndex &index, int role) const
221  {
222      return sourceData(mapToSource(index), role);
223  }
224  
225  QHash<int, QByteArray> QQmlSortFilterProxyModel::roleNames() const
226  {
227      return m_roleNames.isEmpty() && sourceModel() ? sourceModel()->roleNames() : m_roleNames;
228  }
229  
230  /*!
231      \qmlmethod int SortFilterProxyModel::roleForName(string roleName)
232  
233      Returns the role number for the given \a roleName.
234      If no role is found for this \a roleName, \c -1 is returned.
235  */
236  
237  int QQmlSortFilterProxyModel::roleForName(const QString& roleName) const
238  {
239      return m_roleNames.key(roleName.toUtf8(), -1);
240  }
241  
242  /*!
243      \qmlmethod object SortFilterProxyModel::get(int row)
244  
245      Return the item at \a row in the proxy model as a map of all its roles. This allows the item data to be read (not modified) from JavaScript.
246  */
247  QVariantMap QQmlSortFilterProxyModel::get(int row) const
248  {
249      QVariantMap map;
250      QModelIndex modelIndex = index(row, 0);
251      QHash<int, QByteArray> roles = roleNames();
252      for (auto it = roles.cbegin(); it != roles.cend(); ++it)
253          map.insert(it.value(), data(modelIndex, it.key()));
254      return map;
255  }
256  
257  /*!
258      \qmlmethod variant SortFilterProxyModel::get(int row, string roleName)
259  
260      Return the data for the given \a roleName of the item at \a row in the proxy model. This allows the role data to be read (not modified) from JavaScript.
261      This equivalent to calling \c {data(index(row, 0), roleForName(roleName))}.
262  */
263  QVariant QQmlSortFilterProxyModel::get(int row, const QString& roleName) const
264  {
265      return data(index(row, 0), roleForName(roleName));
266  }
267  
268  /*!
269      \qmlmethod index SortFilterProxyModel::mapToSource(index proxyIndex)
270  
271      Returns the source model index corresponding to the given \a proxyIndex from the SortFilterProxyModel.
272  */
273  QModelIndex QQmlSortFilterProxyModel::mapToSource(const QModelIndex& proxyIndex) const
274  {
275      return QSortFilterProxyModel::mapToSource(proxyIndex);
276  }
277  
278  /*!
279      \qmlmethod int SortFilterProxyModel::mapToSource(int proxyRow)
280  
281      Returns the source model row corresponding to the given \a proxyRow from the SortFilterProxyModel.
282      Returns -1 if there is no corresponding row.
283  */
284  int QQmlSortFilterProxyModel::mapToSource(int proxyRow) const
285  {
286      QModelIndex proxyIndex = index(proxyRow, 0);
287      QModelIndex sourceIndex = mapToSource(proxyIndex);
288      return sourceIndex.isValid() ? sourceIndex.row() : -1;
289  }
290  
291  /*!
292      \qmlmethod QModelIndex SortFilterProxyModel::mapFromSource(QModelIndex sourceIndex)
293  
294      Returns the model index in the SortFilterProxyModel given the \a sourceIndex from the source model.
295  */
296  QModelIndex QQmlSortFilterProxyModel::mapFromSource(const QModelIndex& sourceIndex) const
297  {
298      return QSortFilterProxyModel::mapFromSource(sourceIndex);
299  }
300  
301  /*!
302      \qmlmethod int SortFilterProxyModel::mapFromSource(int sourceRow)
303  
304      Returns the row in the SortFilterProxyModel given the \a sourceRow from the source model.
305      Returns -1 if there is no corresponding row.
306  */
307  int QQmlSortFilterProxyModel::mapFromSource(int sourceRow) const
308  {
309      QModelIndex proxyIndex;
310      if (QAbstractItemModel* source = sourceModel()) {
311          QModelIndex sourceIndex = source->index(sourceRow, 0);
312          proxyIndex = mapFromSource(sourceIndex);
313      }
314      return proxyIndex.isValid() ? proxyIndex.row() : -1;
315  }
316  
317  bool QQmlSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
318  {
319      if (!m_completed)
320          return true;
321      QModelIndex sourceIndex = sourceModel()->index(source_row, 0, source_parent);
322      bool valueAccepted = !m_filterValue.isValid() || ( m_filterValue == sourceModel()->data(sourceIndex, filterRole()) );
323      bool baseAcceptsRow = valueAccepted && QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
324      baseAcceptsRow = baseAcceptsRow && std::all_of(m_filters.begin(), m_filters.end(),
325          [=] (Filter* filter) {
326              return filter->filterAcceptsRow(sourceIndex, *this);
327          }
328      );
329      return baseAcceptsRow;
330  }
331  
332  bool QQmlSortFilterProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
333  {
334      if (m_completed) {
335          if (!m_sortRoleName.isEmpty()) {
336              if (QSortFilterProxyModel::lessThan(source_left, source_right))
337                  return m_ascendingSortOrder;
338              if (QSortFilterProxyModel::lessThan(source_right, source_left))
339                  return !m_ascendingSortOrder;
340          }
341          auto sortedSorters = m_sorters;
342          std::stable_sort(sortedSorters.begin(),
343                           sortedSorters.end(),
344                           [] (Sorter* a, Sorter* b) {
345                               return a->priority() > b->priority();
346                           });
347          for(auto sorter : sortedSorters) {
348              if (sorter->enabled()) {
349                  int comparison = sorter->compareRows(source_left, source_right, *this);
350                  if (comparison != 0)
351                      return comparison < 0;
352              }
353          }
354      }
355      return source_left.row() < source_right.row();
356  }
357  
358  void QQmlSortFilterProxyModel::resetInternalData()
359  {
360      QSortFilterProxyModel::resetInternalData();
361      updateRoleNames();
362  }
363  
364  void QQmlSortFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel)
365  {
366      if (this->sourceModel() == sourceModel)
367          return;
368  
369      QSortFilterProxyModel::setSourceModel(sourceModel);
370  
371      // QML built-in type ListModel behaves in a specific way regarding roles
372      // initialization (QTBUG-57971). Empty model has no roles, they become
373      // available after first insertion. However modelAboutToBeReset/modelReset
374      // is not emited. It means that roles may change not only in between model
375      // resets but also on the first insertion.
376      //
377      // The provided workaround causes that initial insert initializing roles in
378      // ListModel is converted into a model reset emited from SFPM. Thanks to
379      // that the roles are not only correctly initialized but also the issue is
380      // no longer propagated to other proxies using SFPM as it's source (because
381      // SFPM will emit model reset instead of insertions signals in that special
382      // case).
383      auto disconnect = [this] {
384          delete m_qtbug_57971_signalsContext;
385          m_qtbug_57971_signalsContext = nullptr;
386      };
387  
388      disconnect();
389  
390      if (sourceModel && sourceModel->roleNames().isEmpty()
391          && sourceModel->rowCount() == 0) {
392  
393          auto& ctx = m_qtbug_57971_signalsContext;
394          ctx = new QObject(this);
395  
396          using QAIM = QAbstractItemModel;
397  
398          connect(sourceModel, &QAIM::rowsAboutToBeInserted, ctx, [this] () {
399              this->beginResetModel();
400              this->blockSignals(true);
401          });
402  
403          connect(sourceModel, &QAIM::rowsInserted, ctx, [this, disconnect] () {
404              this->blockSignals(false);
405              this->initRoles();
406              this->endResetModel();
407  
408              disconnect();
409          });
410  
411          connect(sourceModel, &QAIM::modelReset, ctx, [this, disconnect] () {
412              if (!this->sourceModel()->roleNames().isEmpty()
413                  || this->sourceModel()->rowCount() != 0)
414                  disconnect();
415          });
416      }
417  }
418  
419  void QQmlSortFilterProxyModel::queueInvalidateFilter()
420  {
421      if (m_delayed) {
422          if (!m_invalidateFilterQueued && !m_invalidateQueued) {
423              m_invalidateFilterQueued = true;
424              QMetaObject::invokeMethod(this, "invalidateFilter", Qt::QueuedConnection);
425          }
426      } else {
427          invalidateFilter();
428      }
429  }
430  
431  void QQmlSortFilterProxyModel::invalidateFilter()
432  {
433      m_invalidateFilterQueued = false;
434      if (m_completed && !m_invalidateQueued)
435          QSortFilterProxyModel::invalidateFilter();
436  }
437  
438  void QQmlSortFilterProxyModel::queueInvalidate()
439  {
440      if (m_delayed) {
441          if (!m_invalidateQueued) {
442              m_invalidateQueued = true;
443              QMetaObject::invokeMethod(this, "invalidate", Qt::QueuedConnection);
444          }
445      } else {
446          invalidate();
447      }
448  }
449  
450  void QQmlSortFilterProxyModel::invalidate()
451  {
452      m_invalidateQueued = false;
453      if (m_completed)
454          QSortFilterProxyModel::invalidate();
455  }
456  
457  void QQmlSortFilterProxyModel::updateRoleNames()
458  {
459      if (!sourceModel())
460          return;
461      m_roleNames = sourceModel()->roleNames();
462      m_proxyRoleMap.clear();
463      m_proxyRoleNumbers.clear();
464  
465      auto roles = m_roleNames.keys();
466      auto maxIt = std::max_element(roles.cbegin(), roles.cend());
467      int maxRole = maxIt != roles.cend() ? *maxIt : -1;
468      for (auto proxyRole : qAsConst(m_proxyRoles)) {
469          const auto proxyRoleNames = proxyRole->names();
470          for (const auto &roleName : proxyRoleNames) {
471              ++maxRole;
472              m_roleNames[maxRole] = roleName.toUtf8();
473              m_proxyRoleMap[maxRole] = {proxyRole, roleName};
474              m_proxyRoleNumbers.append(maxRole);
475          }
476      }
477  }
478  
479  void QQmlSortFilterProxyModel::updateFilterRole()
480  {
481      QList<int> filterRoles = roleNames().keys(m_filterRoleName.toUtf8());
482      if (!filterRoles.empty())
483      {
484          setFilterRole(filterRoles.first());
485      }
486  }
487  
488  void QQmlSortFilterProxyModel::updateSortRole()
489  {
490      QList<int> sortRoles = roleNames().keys(m_sortRoleName.toUtf8());
491      if (!sortRoles.empty())
492      {
493          setSortRole(sortRoles.first());
494          queueInvalidate();
495      }
496  }
497  
498  void QQmlSortFilterProxyModel::updateRoles()
499  {
500      updateFilterRole();
501      updateSortRole();
502  }
503  
504  void QQmlSortFilterProxyModel::initRoles()
505  {
506      disconnect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &QQmlSortFilterProxyModel::initRoles);
507      resetInternalData();
508      updateRoles();
509  }
510  
511  void QQmlSortFilterProxyModel::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
512  {
513      Q_UNUSED(roles);
514      if (!roles.isEmpty() && !m_proxyRoleNumbers.empty() && roles != m_proxyRoleNumbers)
515          Q_EMIT dataChanged(topLeft, bottomRight, m_proxyRoleNumbers);
516  }
517  
518  void QQmlSortFilterProxyModel::queueInvalidateProxyRoles()
519  {
520      queueInvalidate();
521      if (m_delayed) {
522          if (!m_invalidateProxyRolesQueued) {
523              m_invalidateProxyRolesQueued = true;
524              QMetaObject::invokeMethod(this, "invalidateProxyRoles", Qt::QueuedConnection);
525          }
526      } else {
527          invalidateProxyRoles();
528      }
529  }
530  
531  void QQmlSortFilterProxyModel::invalidateProxyRoles()
532  {
533      m_invalidateProxyRolesQueued = false;
534      if (m_completed)
535          Q_EMIT dataChanged(index(0,0), index(rowCount() - 1, columnCount() - 1), m_proxyRoleNumbers);
536  }
537  
538  QVariantMap QQmlSortFilterProxyModel::modelDataMap(const QModelIndex& modelIndex) const
539  {
540      QVariantMap map;
541      QHash<int, QByteArray> roles = roleNames();
542      for (auto it = roles.cbegin(); it != roles.cend(); ++it)
543          map.insert(it.value(), sourceModel()->data(modelIndex, it.key()));
544      return map;
545  }
546  
547  void QQmlSortFilterProxyModel::onFilterAppended(Filter* filter)
548  {
549      connect(filter, &Filter::invalidated, this, &QQmlSortFilterProxyModel::queueInvalidateFilter);
550      queueInvalidateFilter();
551  }
552  
553  void QQmlSortFilterProxyModel::onFilterRemoved(Filter* filter)
554  {
555      Q_UNUSED(filter)
556      queueInvalidateFilter();
557  }
558  
559  void QQmlSortFilterProxyModel::onFiltersCleared()
560  {
561      queueInvalidateFilter();
562  }
563  
564  void QQmlSortFilterProxyModel::onSorterAppended(Sorter* sorter)
565  {
566      connect(sorter, &Sorter::invalidated, this, &QQmlSortFilterProxyModel::queueInvalidate);
567      queueInvalidate();
568  }
569  
570  void QQmlSortFilterProxyModel::onSorterRemoved(Sorter* sorter)
571  {
572      Q_UNUSED(sorter)
573      queueInvalidate();
574  }
575  
576  void QQmlSortFilterProxyModel::onSortersCleared()
577  {
578      queueInvalidate();
579  }
580  
581  void QQmlSortFilterProxyModel::onProxyRoleAppended(ProxyRole *proxyRole)
582  {
583      beginResetModel();
584      connect(proxyRole, &ProxyRole::invalidated, this, &QQmlSortFilterProxyModel::queueInvalidateProxyRoles);
585      connect(proxyRole, &ProxyRole::namesAboutToBeChanged, this, &QQmlSortFilterProxyModel::beginResetModel);
586      connect(proxyRole, &ProxyRole::namesChanged, this, &QQmlSortFilterProxyModel::endResetModel);
587      endResetModel();
588  }
589  
590  void QQmlSortFilterProxyModel::onProxyRoleRemoved(ProxyRole *proxyRole)
591  {
592      Q_UNUSED(proxyRole)
593      beginResetModel();
594      endResetModel();
595  }
596  
597  void QQmlSortFilterProxyModel::onProxyRolesCleared()
598  {
599      beginResetModel();
600      endResetModel();
601  }
602  
603  void registerQQmlSortFilterProxyModelTypes() {
604      qmlRegisterType<QQmlSortFilterProxyModel>("SortFilterProxyModel", 0, 2, "SortFilterProxyModel");
605  }
606  
607  Q_COREAPP_STARTUP_FUNCTION(registerQQmlSortFilterProxyModelTypes)
608  
609  }