| 1 | // Copyright (C) 2025 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include <QtQmlModels/private/qqmlsortercompositor_p.h> |
| 5 | #include <QtQmlModels/private/qqmlsortfilterproxymodel_p.h> |
| 6 | |
| 7 | QT_BEGIN_NAMESPACE |
| 8 | |
| 9 | QQmlSorterCompositor::QQmlSorterCompositor(QObject *parent) |
| 10 | : QQmlSorterBase(new QQmlSorterCompositorPrivate, parent) |
| 11 | { |
| 12 | Q_D(QQmlSorterCompositor); |
| 13 | d->init(); |
| 14 | // Connect the model reset with the update in the filter |
| 15 | // The cache need to be updated once the model is reset with the |
| 16 | // source model data. |
| 17 | connect(sender: d->m_sfpmModel, signal: &QQmlSortFilterProxyModel::modelReset, |
| 18 | context: this, slot: &QQmlSorterCompositor::updateSorters); |
| 19 | connect(sender: d->m_sfpmModel, signal: &QQmlSortFilterProxyModel::primarySorterChanged, |
| 20 | context: this, slot: &QQmlSorterCompositor::updateEffectiveSorters); |
| 21 | } |
| 22 | |
| 23 | QQmlSorterCompositor::~QQmlSorterCompositor() |
| 24 | { |
| 25 | |
| 26 | } |
| 27 | |
| 28 | void QQmlSorterCompositorPrivate::init() |
| 29 | { |
| 30 | Q_Q(QQmlSorterCompositor); |
| 31 | m_sfpmModel = qobject_cast<QQmlSortFilterProxyModel *>(object: q->parent()); |
| 32 | } |
| 33 | |
| 34 | void QQmlSorterCompositor::append(QQmlListProperty<QQmlSorterBase> *sorterComp, QQmlSorterBase* sorter) |
| 35 | { |
| 36 | auto *sorterCompositor = reinterpret_cast<QQmlSorterCompositor *>(sorterComp->object); |
| 37 | sorterCompositor->append(sorter); |
| 38 | } |
| 39 | |
| 40 | qsizetype QQmlSorterCompositor::count(QQmlListProperty<QQmlSorterBase> *sorterComp) |
| 41 | { |
| 42 | auto *sorterCompositor = reinterpret_cast<QQmlSorterCompositor *> (sorterComp->object); |
| 43 | return sorterCompositor->count(); |
| 44 | } |
| 45 | |
| 46 | QQmlSorterBase *QQmlSorterCompositor::at(QQmlListProperty<QQmlSorterBase> *sorterComp, qsizetype index) |
| 47 | { |
| 48 | auto *sorterCompositor = reinterpret_cast<QQmlSorterCompositor *> (sorterComp->object); |
| 49 | return sorterCompositor->at(index); |
| 50 | } |
| 51 | |
| 52 | void QQmlSorterCompositor::clear(QQmlListProperty<QQmlSorterBase> *sorterComp) |
| 53 | { |
| 54 | auto *sorterCompositor = reinterpret_cast<QQmlSorterCompositor *> (sorterComp->object); |
| 55 | sorterCompositor->clear(); |
| 56 | } |
| 57 | |
| 58 | void QQmlSorterCompositor::append(QQmlSorterBase *sorter) |
| 59 | { |
| 60 | if (!sorter) |
| 61 | return; |
| 62 | Q_D(QQmlSorterCompositor); |
| 63 | d->m_sorters.append(t: sorter); |
| 64 | // Update sorter cache depending on the priority |
| 65 | updateCache(); |
| 66 | // Connect the sorter to the corresponding slot to invalidate the model |
| 67 | // and the sorter cache |
| 68 | QObject::connect(sender: sorter, signal: &QQmlSorterBase::invalidateModel, |
| 69 | context: d->m_sfpmModel, slot: &QQmlSortFilterProxyModel::invalidate); |
| 70 | // This is needed as its required to update cache when there is any |
| 71 | // change in the filter itself (for instance, a change in the priority of |
| 72 | // the filter) |
| 73 | QObject::connect(sender: sorter, signal: &QQmlSorterBase::invalidateCache, |
| 74 | context: this, slot: &QQmlSorterCompositor::updateCache); |
| 75 | // Reset the primary sort column when any sort order or column |
| 76 | // changed |
| 77 | QObject::connect(sender: sorter, signal: &QQmlSorterBase::sortOrderChanged, |
| 78 | context: this, slot: [d] { d->resetPrimarySorter(); }); |
| 79 | QObject::connect(sender: sorter, signal: &QQmlSorterBase::columnChanged, |
| 80 | context: this, slot: [d] { d->resetPrimarySorter(); }); |
| 81 | // Since we added new filter to the list, emit the filter changed signal |
| 82 | // for the filters thats been appended to the list |
| 83 | emit d->m_sfpmModel->sortersChanged(); |
| 84 | } |
| 85 | |
| 86 | qsizetype QQmlSorterCompositor::count() |
| 87 | { |
| 88 | Q_D(QQmlSorterCompositor); |
| 89 | return d->m_sorters.count(); |
| 90 | } |
| 91 | |
| 92 | QQmlSorterBase* QQmlSorterCompositor::at(qsizetype index) |
| 93 | { |
| 94 | Q_D(QQmlSorterCompositor); |
| 95 | return d->m_sorters.at(i: index); |
| 96 | } |
| 97 | |
| 98 | void QQmlSorterCompositor::clear() |
| 99 | { |
| 100 | Q_D(QQmlSorterCompositor); |
| 101 | d->m_effectiveSorters.clear(); |
| 102 | d->m_sorters.clear(); |
| 103 | // Emit the filter changed signal as we cleared the filter list |
| 104 | emit d->m_sfpmModel->sortersChanged(); |
| 105 | } |
| 106 | |
| 107 | QList<QQmlSorterBase *> QQmlSorterCompositor::sorters() |
| 108 | { |
| 109 | Q_D(QQmlSorterCompositor); |
| 110 | return d->m_sorters; |
| 111 | } |
| 112 | |
| 113 | QQmlListProperty<QQmlSorterBase> QQmlSorterCompositor::sortersListProperty() |
| 114 | { |
| 115 | Q_D(QQmlSorterCompositor); |
| 116 | return QQmlListProperty<QQmlSorterBase>(reinterpret_cast<QObject*>(this), &d->m_sorters, |
| 117 | QQmlSorterCompositor::append, |
| 118 | QQmlSorterCompositor::count, |
| 119 | QQmlSorterCompositor::at, |
| 120 | QQmlSorterCompositor::clear); |
| 121 | } |
| 122 | |
| 123 | void QQmlSorterCompositor::updateEffectiveSorters() |
| 124 | { |
| 125 | Q_D(QQmlSorterCompositor); |
| 126 | |
| 127 | if (!d->m_primarySorter || !d->m_primarySorter->enabled()) { |
| 128 | updateCache(); |
| 129 | return; |
| 130 | } |
| 131 | |
| 132 | QList<QQmlSorterBase *> sorters; |
| 133 | sorters.append(t: d->m_primarySorter); |
| 134 | std::copy_if(first: d->m_effectiveSorters.constBegin(), last: d->m_effectiveSorters.constEnd(), |
| 135 | result: std::back_inserter(x&: sorters), pred: [d](QQmlSorterBase *sorter){ |
| 136 | // Consider only the filters that are enabled and exclude the primary |
| 137 | // sorter as its already added to the list |
| 138 | return (sorter != d->m_primarySorter); |
| 139 | }); |
| 140 | d->m_effectiveSorters = sorters; |
| 141 | } |
| 142 | |
| 143 | void QQmlSorterCompositor::updateSorters() |
| 144 | { |
| 145 | Q_D(QQmlSorterCompositor); |
| 146 | // Update sorters that has dependency with the model data to determine |
| 147 | // whether it needs to be included or not |
| 148 | for (auto &sorter: d->m_sorters) |
| 149 | sorter->update(d->m_sfpmModel); |
| 150 | updateCache(); |
| 151 | } |
| 152 | |
| 153 | void QQmlSorterCompositor::updateCache() |
| 154 | { |
| 155 | Q_D(QQmlSorterCompositor); |
| 156 | // Clear the existing cache |
| 157 | d->m_effectiveSorters.clear(); |
| 158 | if (d->m_sfpmModel && d->m_sfpmModel->sourceModel()) { |
| 159 | // Sort the filter according to their priority |
| 160 | QList<QQmlSorterBase *> sorters = d->m_sorters; |
| 161 | std::stable_sort(first: sorters.begin(), last: sorters.end(), |
| 162 | comp: [](QQmlSorterBase *sorterLeft, QQmlSorterBase *sorterRight) { |
| 163 | return sorterLeft->priority() < sorterRight->priority(); |
| 164 | }); |
| 165 | // Cache only the filters that are need to be evaluated (in order) |
| 166 | std::copy_if(first: sorters.begin(), last: sorters.end(), result: std::back_inserter(x&: d->m_effectiveSorters), |
| 167 | pred: [](QQmlSorterBase *sorter) { return sorter->enabled(); }); |
| 168 | // If there is no primary sorter set by the user explicitly, reset the |
| 169 | // primary sorter according to the sorters in the lists |
| 170 | d->resetPrimarySorter(); |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | bool QQmlSorterCompositor::lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel *proxyModel) const |
| 175 | { |
| 176 | Q_D(const QQmlSorterCompositor); |
| 177 | for (const auto &sorter : d->m_effectiveSorters) { |
| 178 | const int sortSection = sorter->column(); |
| 179 | if ((sortSection > -1) && (sortSection < proxyModel->sourceModel()->columnCount())) { |
| 180 | const auto *sourceModel = proxyModel->sourceModel(); |
| 181 | const QPartialOrdering result = sorter->compare(sourceModel->index(row: sourceLeft.row(), column: sortSection), |
| 182 | sourceModel->index(row: sourceRight.row(), column: sortSection), |
| 183 | proxyModel); |
| 184 | if ((result == QPartialOrdering::Less) || (result == QPartialOrdering::Greater)) |
| 185 | return (result < 0); |
| 186 | } |
| 187 | } |
| 188 | // Verify the index order when the ordering is either equal or unordered |
| 189 | return sourceLeft.row() < sourceRight.row(); |
| 190 | } |
| 191 | |
| 192 | void QQmlSorterCompositorPrivate::setPrimarySorter(QQmlSorterBase *sorter) |
| 193 | { |
| 194 | if (sorter == nullptr || |
| 195 | (std::find(first: m_sorters.constBegin(), last: m_sorters.constEnd(), val: sorter) != m_sorters.constEnd())) { |
| 196 | m_primarySorter = sorter; |
| 197 | if (m_primarySorter && m_primarySorter->enabled()) { |
| 198 | m_sfpmModel->setPrimarySortOrder(m_primarySorter->sortOrder()); |
| 199 | m_sfpmModel->setPrimarySortColumn(m_primarySorter->column()); |
| 200 | return; |
| 201 | } |
| 202 | } |
| 203 | resetPrimarySorter(); |
| 204 | } |
| 205 | |
| 206 | void QQmlSorterCompositorPrivate::resetPrimarySorter() |
| 207 | { |
| 208 | if (!m_primarySorter) { |
| 209 | if (!m_effectiveSorters.isEmpty()) { |
| 210 | // Set the primary sort column and its order to the proxy model |
| 211 | const auto *sorter = m_effectiveSorters.at(i: 0); |
| 212 | m_sfpmModel->setPrimarySortOrder(sorter->sortOrder()); |
| 213 | m_sfpmModel->setPrimarySortColumn(sorter->column()); |
| 214 | } else { |
| 215 | // By default reset the sort order to ascending order and the |
| 216 | // column to 0 |
| 217 | m_sfpmModel->setPrimarySortOrder(Qt::AscendingOrder); |
| 218 | m_sfpmModel->setPrimarySortColumn(0); |
| 219 | } |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | QT_END_NAMESPACE |
| 224 | |
| 225 | #include "moc_qqmlsortercompositor_p.cpp" |
| 226 | |