File indexing completed on 2024-12-01 13:09:06

0001 /*
0002    SPDX-FileCopyrightText: 2021 David Faure <faure@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "roomlistheadingsproxymodel.h"
0007 
0008 #include <QApplication>
0009 #include <QPalette>
0010 
0011 RoomListHeadingsProxyModel::RoomListHeadingsProxyModel(QObject *parent)
0012     : QAbstractProxyModel(parent)
0013 {
0014 }
0015 
0016 void RoomListHeadingsProxyModel::setSourceModel(QAbstractItemModel *sourceModel)
0017 {
0018     beginResetModel();
0019 
0020     if (auto *oldModel = this->sourceModel()) {
0021         disconnect(oldModel, nullptr, this, nullptr);
0022     }
0023 
0024     QAbstractProxyModel::setSourceModel(sourceModel);
0025 
0026     connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &RoomListHeadingsProxyModel::onRowsInserted);
0027 
0028     connect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &RoomListHeadingsProxyModel::onRowsAboutToBeRemoved);
0029 
0030     connect(sourceModel, &QAbstractItemModel::rowsAboutToBeMoved, this, [this] {
0031         Q_EMIT layoutAboutToBeChanged();
0032     });
0033     connect(sourceModel, &QAbstractItemModel::rowsMoved, this, &RoomListHeadingsProxyModel::rebuildSections);
0034     connect(sourceModel, &QAbstractItemModel::rowsMoved, this, [this] {
0035         Q_EMIT layoutChanged();
0036     });
0037 
0038     connect(sourceModel, &QAbstractItemModel::dataChanged, this, &RoomListHeadingsProxyModel::onDataChanged);
0039 
0040     connect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, this, &RoomListHeadingsProxyModel::beginResetModel);
0041     connect(sourceModel, &QAbstractItemModel::modelReset, this, &RoomListHeadingsProxyModel::rebuildSections);
0042     connect(sourceModel, &QAbstractItemModel::modelReset, this, &RoomListHeadingsProxyModel::endResetModel);
0043 
0044     connect(sourceModel, &QAbstractItemModel::layoutAboutToBeChanged, this, &RoomListHeadingsProxyModel::layoutAboutToBeChanged);
0045     connect(sourceModel, &QAbstractItemModel::layoutChanged, this, &RoomListHeadingsProxyModel::rebuildSections);
0046     connect(sourceModel, &QAbstractItemModel::layoutChanged, this, &RoomListHeadingsProxyModel::layoutChanged);
0047 
0048     rebuildSections();
0049 
0050     endResetModel();
0051 }
0052 
0053 int RoomListHeadingsProxyModel::rowCount(const QModelIndex &parent) const
0054 {
0055     switch (type(parent)) {
0056     case IndexType::Root:
0057         return sectionCount;
0058     case IndexType::Section:
0059         return int(mSections.at(parent.row()).size());
0060     case IndexType::Channel:
0061         return 0;
0062     }
0063     Q_UNREACHABLE();
0064 }
0065 
0066 int RoomListHeadingsProxyModel::columnCount(const QModelIndex &parent) const
0067 {
0068     switch (type(parent)) {
0069     case IndexType::Root:
0070     case IndexType::Section:
0071         return 1;
0072     case IndexType::Channel:
0073         return 0;
0074     }
0075     Q_UNREACHABLE();
0076 }
0077 
0078 bool RoomListHeadingsProxyModel::hasChildren(const QModelIndex &index) const
0079 {
0080     // Don't use the QAbstractProxyModel override which fails on non-proxied indices
0081     return QAbstractItemModel::hasChildren(index); // NOLINT(bugprone-parent-virtual-call)
0082 }
0083 
0084 QModelIndex RoomListHeadingsProxyModel::index(int row, int column, const QModelIndex &parent) const
0085 {
0086     switch (type(parent)) {
0087     case IndexType::Root:
0088         return createIndex(row, column, sectionCount);
0089     case IndexType::Section:
0090         return createIndex(row, column, parent.row());
0091     case IndexType::Channel:
0092         return {};
0093     }
0094     Q_UNREACHABLE();
0095 }
0096 
0097 QModelIndex RoomListHeadingsProxyModel::parent(const QModelIndex &child) const
0098 {
0099     switch (type(child)) {
0100     case IndexType::Root:
0101     case IndexType::Section:
0102         return {};
0103     case IndexType::Channel:
0104         return createIndex(int(child.internalId()), 0, sectionCount);
0105     }
0106     Q_UNREACHABLE();
0107 }
0108 
0109 QVariant RoomListHeadingsProxyModel::data(const QModelIndex &index, int role) const
0110 {
0111     switch (type(index)) {
0112     case IndexType::Root:
0113         return {};
0114     case IndexType::Section:
0115         switch (role) {
0116         case Qt::ItemDataRole::DisplayRole:
0117             return RoomModel::sectionName(RoomModel::Section(index.row()));
0118         case Qt::BackgroundRole:
0119             return QApplication::palette().brush(QPalette::Window);
0120         default:
0121             return {};
0122         }
0123     case IndexType::Channel:
0124         return sourceModel()->data(mapToSource(index), role);
0125     }
0126     Q_UNREACHABLE();
0127 }
0128 
0129 QModelIndex RoomListHeadingsProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
0130 {
0131     if (!sourceModel())
0132         return {};
0133 
0134     if (!sourceIndex.isValid())
0135         return {};
0136 
0137     for (auto sectionId = size_t(0), iMax = mSections.size(); sectionId < iMax; ++sectionId) {
0138         const auto &section = mSections.at(sectionId);
0139 
0140         const auto it = std::lower_bound(section.cbegin(), section.cend(), sourceIndex);
0141         if (it != section.cend() && *it == sourceIndex)
0142             return createIndex(int(it - section.cbegin()), 0, sectionId);
0143     }
0144 
0145     return {};
0146 }
0147 
0148 QModelIndex RoomListHeadingsProxyModel::mapToSource(const QModelIndex &proxyIndex) const
0149 {
0150     if (!sourceModel())
0151         return {};
0152 
0153     switch (type(proxyIndex)) {
0154     case IndexType::Root:
0155     case IndexType::Section:
0156         return {};
0157     case IndexType::Channel:
0158         return mSections.at(proxyIndex.internalId()).at(proxyIndex.row());
0159     }
0160     Q_UNREACHABLE();
0161 }
0162 
0163 Qt::ItemFlags RoomListHeadingsProxyModel::flags(const QModelIndex &proxyIndex) const
0164 {
0165     switch (type(proxyIndex)) {
0166     case IndexType::Root:
0167         return {};
0168     case IndexType::Section:
0169         return Qt::ItemFlag::ItemIsEnabled;
0170     case IndexType::Channel:
0171         return QAbstractProxyModel::flags(proxyIndex);
0172     }
0173     Q_UNREACHABLE();
0174 }
0175 
0176 void RoomListHeadingsProxyModel::onRowsInserted(const QModelIndex &parent, int first, int last)
0177 {
0178     for (auto row = first; row <= last; ++row) {
0179         const QPersistentModelIndex index = sourceModel()->index(row, 0, parent);
0180         const auto newSectionId = int(index.data(RoomModel::RoomSection).value<RoomModel::Section>());
0181         auto &newSection = mSections.at(newSectionId);
0182 
0183         const auto newLocation = std::lower_bound(newSection.cbegin(), newSection.cend(), index);
0184         const auto newLocationRow = int(newLocation - newSection.cbegin());
0185 
0186         beginInsertRows(createIndex(newSectionId, 0, sectionCount), newLocationRow, newLocationRow);
0187 
0188         newSection.insert(newLocation, index);
0189 
0190         endInsertRows();
0191     }
0192 }
0193 
0194 void RoomListHeadingsProxyModel::onRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
0195 {
0196     for (auto row = first; row <= last; ++row) {
0197         const auto index = sourceModel()->index(row, 0, parent);
0198 
0199         const auto ourOldIndex = mapFromSource(index);
0200         const auto oldSectionId = ourOldIndex.internalId();
0201         auto &oldSection = mSections.at(oldSectionId);
0202 
0203         beginRemoveRows(ourOldIndex.parent(), ourOldIndex.row(), ourOldIndex.row());
0204 
0205         oldSection.erase(oldSection.begin() + ourOldIndex.row());
0206 
0207         endRemoveRows();
0208     }
0209 }
0210 
0211 void RoomListHeadingsProxyModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
0212 {
0213     for (auto row = topLeft.row(), last = bottomRight.row(); row <= last; ++row) {
0214         const auto sourceIndex = topLeft.siblingAtRow(row);
0215         const auto proxyIndex = mapFromSource(sourceIndex);
0216         Q_EMIT dataChanged(proxyIndex, proxyIndex, roles);
0217     }
0218 
0219     if (!roles.empty() && !roles.contains(RoomModel::RoomSection))
0220         return;
0221 
0222     for (auto row = topLeft.row(), last = bottomRight.row(); row <= last; ++row) {
0223         const auto sourceIndex = topLeft.siblingAtRow(row);
0224         const auto ourOldIndex = mapFromSource(sourceIndex);
0225 
0226         const auto oldSectionId = int(ourOldIndex.internalId());
0227         const auto newSectionId = int(sourceIndex.data(RoomModel::RoomSection).value<RoomModel::Section>());
0228 
0229         if (oldSectionId == newSectionId)
0230             continue;
0231 
0232         auto &oldSection = mSections.at(oldSectionId);
0233         auto &newSection = mSections.at(newSectionId);
0234 
0235         const auto newLocation = std::lower_bound(newSection.cbegin(), newSection.cend(), sourceIndex);
0236         const auto newLocationRow = int(newLocation - newSection.cbegin());
0237 
0238         beginMoveRows(ourOldIndex.parent(), ourOldIndex.row(), ourOldIndex.row(), createIndex(newSectionId, 0, sectionCount), newLocationRow);
0239 
0240         auto persistantIndex = oldSection[ourOldIndex.row()];
0241         oldSection.erase(oldSection.begin() + ourOldIndex.row());
0242         newSection.insert(newLocation, persistantIndex);
0243 
0244         endMoveRows();
0245     }
0246 }
0247 
0248 void RoomListHeadingsProxyModel::rebuildSections()
0249 {
0250     for (auto &section : mSections)
0251         section.clear();
0252 
0253     for (auto row = 0, until = sourceModel()->rowCount(); row < until; ++row) {
0254         const QPersistentModelIndex index = sourceModel()->index(row, 0);
0255         const auto newSectionId = uint(index.data(RoomModel::RoomSection).value<RoomModel::Section>());
0256         auto &newSection = mSections.at(newSectionId);
0257 
0258         newSection.push_back(index);
0259     }
0260 
0261     for (auto &section : mSections)
0262         std::sort(section.begin(), section.end());
0263 }
0264 
0265 auto RoomListHeadingsProxyModel::type(const QModelIndex &index) const -> IndexType
0266 {
0267     if (!index.isValid())
0268         return IndexType::Root;
0269 
0270     if (index.internalId() == sectionCount)
0271         return IndexType::Section;
0272 
0273     return IndexType::Channel;
0274 }
0275 
0276 #include "moc_roomlistheadingsproxymodel.cpp"