/ 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 }