File indexing completed on 2025-01-26 05:06:22

0001 /*
0002     SPDX-FileCopyrightText: 2014 Eike Hein <hein@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "positioner.h"
0008 #include "foldermodel.h"
0009 
0010 #include <cstdlib>
0011 #include <span>
0012 
0013 #include <QTimer>
0014 
0015 Positioner::Positioner(QObject *parent)
0016     : QAbstractItemModel(parent)
0017     , m_enabled(false)
0018     , m_folderModel(nullptr)
0019     , m_perStripe(0)
0020     , m_ignoreNextTransaction(false)
0021     , m_deferApplyPositions(false)
0022     , m_updatePositionsTimer(new QTimer(this))
0023 {
0024     m_updatePositionsTimer->setSingleShot(true);
0025     m_updatePositionsTimer->setInterval(0);
0026     connect(m_updatePositionsTimer, &QTimer::timeout, this, &Positioner::updatePositions);
0027 }
0028 
0029 Positioner::~Positioner()
0030 {
0031 }
0032 
0033 bool Positioner::enabled() const
0034 {
0035     return m_enabled;
0036 }
0037 
0038 void Positioner::setEnabled(bool enabled)
0039 {
0040     if (m_enabled != enabled) {
0041         m_enabled = enabled;
0042 
0043         beginResetModel();
0044 
0045         if (enabled && m_folderModel) {
0046             initMaps();
0047         }
0048 
0049         endResetModel();
0050 
0051         Q_EMIT enabledChanged();
0052 
0053         if (!enabled) {
0054             m_updatePositionsTimer->start();
0055         }
0056     }
0057 }
0058 
0059 FolderModel *Positioner::folderModel() const
0060 {
0061     return m_folderModel;
0062 }
0063 
0064 void Positioner::setFolderModel(QObject *folderModel)
0065 {
0066     if (m_folderModel != folderModel) {
0067         beginResetModel();
0068 
0069         if (m_folderModel) {
0070             disconnectSignals(m_folderModel);
0071         }
0072 
0073         m_folderModel = qobject_cast<FolderModel *>(folderModel);
0074 
0075         if (m_folderModel) {
0076             connectSignals(m_folderModel);
0077 
0078             if (m_enabled) {
0079                 initMaps();
0080             }
0081         }
0082 
0083         endResetModel();
0084 
0085         Q_EMIT folderModelChanged();
0086     }
0087 }
0088 
0089 int Positioner::perStripe() const
0090 {
0091     return m_perStripe;
0092 }
0093 
0094 void Positioner::setPerStripe(int perStripe)
0095 {
0096     if (m_perStripe != perStripe) {
0097         m_perStripe = perStripe;
0098 
0099         Q_EMIT perStripeChanged();
0100 
0101         if (m_enabled && perStripe > 0 && !m_proxyToSource.isEmpty()) {
0102             applyPositions();
0103         }
0104     }
0105 }
0106 
0107 QStringList Positioner::positions() const
0108 {
0109     return m_positions;
0110 }
0111 
0112 void Positioner::setPositions(const QStringList &positions)
0113 {
0114     if (m_positions != positions) {
0115         m_positions = positions;
0116 
0117         Q_EMIT positionsChanged();
0118 
0119         // Defer applying positions until listing completes.
0120         if (m_folderModel->status() == FolderModel::Listing) {
0121             m_deferApplyPositions = true;
0122         } else {
0123             applyPositions();
0124         }
0125     }
0126 }
0127 
0128 int Positioner::map(int row) const
0129 {
0130     if (m_enabled && m_folderModel) {
0131         return m_proxyToSource.value(row, -1);
0132     }
0133 
0134     return row;
0135 }
0136 
0137 int Positioner::nearestItem(int currentIndex, Qt::ArrowType direction)
0138 {
0139     if (!m_enabled || currentIndex >= rowCount()) {
0140         return -1;
0141     }
0142 
0143     if (currentIndex < 0) {
0144         return firstRow();
0145     }
0146 
0147     int hDirection = 0;
0148     int vDirection = 0;
0149 
0150     switch (direction) {
0151     case Qt::LeftArrow:
0152         hDirection = -1;
0153         break;
0154     case Qt::RightArrow:
0155         hDirection = 1;
0156         break;
0157     case Qt::UpArrow:
0158         vDirection = -1;
0159         break;
0160     case Qt::DownArrow:
0161         vDirection = 1;
0162         break;
0163     default:
0164         return -1;
0165     }
0166 
0167     QList<int> rows(m_proxyToSource.keys());
0168     std::sort(rows.begin(), rows.end());
0169 
0170     int nearestItem = -1;
0171     const QPoint currentPos(currentIndex % m_perStripe, currentIndex / m_perStripe);
0172     int lastDistance = -1;
0173     int distance = 0;
0174 
0175     for (const int row : std::as_const(rows)) {
0176         const QPoint pos(row % m_perStripe, row / m_perStripe);
0177 
0178         if (row == currentIndex) {
0179             continue;
0180         }
0181 
0182         if (hDirection == 0) {
0183             if (vDirection * pos.y() > vDirection * currentPos.y()) {
0184                 distance = (pos - currentPos).manhattanLength();
0185 
0186                 if (nearestItem == -1 || distance < lastDistance || (distance == lastDistance && pos.x() == currentPos.x())) {
0187                     nearestItem = row;
0188                     lastDistance = distance;
0189                 }
0190             }
0191         } else if (vDirection == 0) {
0192             if (hDirection * pos.x() > hDirection * currentPos.x()) {
0193                 distance = (pos - currentPos).manhattanLength();
0194 
0195                 if (nearestItem == -1 || distance < lastDistance || (distance == lastDistance && pos.y() == currentPos.y())) {
0196                     nearestItem = row;
0197                     lastDistance = distance;
0198                 }
0199             }
0200         }
0201     }
0202 
0203     return nearestItem;
0204 }
0205 
0206 bool Positioner::isBlank(int row) const
0207 {
0208     if (!m_enabled && m_folderModel) {
0209         return m_folderModel->isBlank(row);
0210     }
0211 
0212     if (m_proxyToSource.contains(row) && m_folderModel && !m_folderModel->isBlank(m_proxyToSource.value(row))) {
0213         return false;
0214     }
0215 
0216     return true;
0217 }
0218 
0219 int Positioner::indexForUrl(const QUrl &url) const
0220 {
0221     if (!m_folderModel) {
0222         return -1;
0223     }
0224 
0225     const QString &name = url.fileName();
0226 
0227     int sourceIndex = -1;
0228 
0229     // TODO Optimize.
0230     for (int i = 0; i < m_folderModel->rowCount(); ++i) {
0231         if (m_folderModel->data(m_folderModel->index(i, 0), FolderModel::FileNameRole).toString() == name) {
0232             sourceIndex = i;
0233 
0234             break;
0235         }
0236     }
0237 
0238     return m_sourceToProxy.value(sourceIndex, -1);
0239 }
0240 
0241 void Positioner::setRangeSelected(int anchor, int to)
0242 {
0243     if (!m_folderModel) {
0244         return;
0245     }
0246 
0247     if (m_enabled) {
0248         QVariantList indices;
0249 
0250         for (int i = qMin(anchor, to); i <= qMax(anchor, to); ++i) {
0251             if (m_proxyToSource.contains(i)) {
0252                 indices.append(m_proxyToSource.value(i));
0253             }
0254         }
0255 
0256         if (!indices.isEmpty()) {
0257             m_folderModel->updateSelection(indices, false);
0258         }
0259     } else {
0260         m_folderModel->setRangeSelected(anchor, to);
0261     }
0262 }
0263 
0264 QHash<int, QByteArray> Positioner::roleNames() const
0265 {
0266     return FolderModel::staticRoleNames();
0267 }
0268 
0269 QModelIndex Positioner::index(int row, int column, const QModelIndex &parent) const
0270 {
0271     if (parent.isValid()) {
0272         return QModelIndex();
0273     }
0274 
0275     return createIndex(row, column);
0276 }
0277 
0278 QModelIndex Positioner::parent(const QModelIndex &index) const
0279 {
0280     if (m_folderModel) {
0281         m_folderModel->parent(index);
0282     }
0283 
0284     return QModelIndex();
0285 }
0286 
0287 QVariant Positioner::data(const QModelIndex &index, int role) const
0288 {
0289     if (!index.isValid()) {
0290         return QVariant();
0291     }
0292 
0293     if (m_folderModel) {
0294         if (m_enabled) {
0295             if (m_proxyToSource.contains(index.row())) {
0296                 return m_folderModel->data(m_folderModel->index(m_proxyToSource.value(index.row()), 0), role);
0297             } else if (role == FolderModel::BlankRole) {
0298                 return true;
0299             }
0300         } else {
0301             return m_folderModel->data(m_folderModel->index(index.row(), 0), role);
0302         }
0303     }
0304 
0305     return QVariant();
0306 }
0307 
0308 int Positioner::rowCount(const QModelIndex &parent) const
0309 {
0310     if (m_folderModel) {
0311         if (m_enabled) {
0312             if (parent.isValid()) {
0313                 return 0;
0314             } else {
0315                 return lastRow() + 1;
0316             }
0317         } else {
0318             return m_folderModel->rowCount(parent);
0319         }
0320     }
0321 
0322     return 0;
0323 }
0324 
0325 int Positioner::columnCount(const QModelIndex &parent) const
0326 {
0327     Q_UNUSED(parent)
0328 
0329     if (m_folderModel) {
0330         return 1;
0331     }
0332 
0333     return 0;
0334 }
0335 
0336 void Positioner::reset()
0337 {
0338     beginResetModel();
0339 
0340     initMaps();
0341 
0342     endResetModel();
0343 
0344     m_positions = QStringList();
0345     Q_EMIT positionsChanged();
0346 }
0347 
0348 int Positioner::move(const QVariantList &moves)
0349 {
0350     struct RowMove {
0351         int from;
0352         int to;
0353         int sourceRow;
0354     };
0355 
0356     // Don't allow moves while listing.
0357     if (m_folderModel->status() == FolderModel::Listing) {
0358         m_deferMovePositions.append(moves);
0359         return -1;
0360     }
0361 
0362     QList<int> fromIndices;
0363     QList<int> toIndices;
0364     QVariantList sourceRows;
0365     QList<RowMove> actualMoves;
0366 
0367     for (int i = 0; i < moves.count(); ++i) {
0368         const int isFrom = (i % 2 == 0);
0369         const int v = moves[i].toInt();
0370 
0371         if (isFrom) {
0372             if (m_proxyToSource.contains(v)) {
0373                 sourceRows.append(m_proxyToSource.value(v));
0374             } else {
0375                 sourceRows.append(-1);
0376             }
0377         }
0378 
0379         (isFrom ? fromIndices : toIndices).append(v);
0380     }
0381 
0382     const int oldCount = rowCount();
0383     int newEnd = oldCount - 1;
0384     int maxTo = -1;
0385     QSet<int> toBeRemoved;
0386 
0387     // NOTE: this is the same code repeated twice: first it "tries" the move to see what the final count would look like to know if there would be rows
0388     // insertions or removals. then do it for real enclosed in beginRemoveRows/endRemoveRows or beginInsertRows/endRemoveRows
0389     for (int i = 0; i < fromIndices.count(); ++i) {
0390         const int from = fromIndices[i];
0391         int to = toIndices[i];
0392         const int sourceRow = sourceRows[i].toInt();
0393 
0394         if (sourceRow == -1 || from == to) {
0395             continue;
0396         }
0397 
0398         if (to == -1) {
0399             to = firstFreeRow();
0400 
0401             if (to == -1) {
0402                 to = lastRow() + 1;
0403             }
0404         }
0405 
0406         if (!fromIndices.contains(to) && !isBlank(to)) {
0407             /* find the next blank space
0408              * we won't be happy if we're moving two icons to the same place
0409              */
0410             while ((!isBlank(to) && from != to) || toIndices.contains(to)) {
0411                 to++;
0412             }
0413         }
0414 
0415         toIndices[i] = to;
0416 
0417         if (toBeRemoved.contains(to)) {
0418             toBeRemoved.remove(to);
0419         }
0420 
0421         if (from == newEnd) {
0422             for (int i = oldCount - 1; i >= 0 && (isBlank(i) || toBeRemoved.contains(i)); --i) {
0423                 newEnd = i;
0424             }
0425             toBeRemoved.insert(newEnd);
0426         }
0427         maxTo = std::max(maxTo, to);
0428         newEnd = std::max(newEnd, maxTo);
0429         actualMoves.append({from, to, sourceRow});
0430     }
0431 
0432     if (newEnd < oldCount - 1) {
0433         beginRemoveRows(QModelIndex(), newEnd + 1, oldCount - 1);
0434     } else if (newEnd > oldCount - 1 && !m_beginInsertRowsCalled) {
0435         beginInsertRows(QModelIndex(), oldCount, newEnd);
0436     }
0437 
0438     for (const RowMove &move : std::as_const(actualMoves)) {
0439         if (!toIndices.contains(move.from)) {
0440             m_proxyToSource.remove(move.from);
0441         }
0442 
0443         updateMaps(move.to, move.sourceRow);
0444 
0445         const QModelIndex &fromIdx = index(move.from, 0);
0446         Q_EMIT dataChanged(fromIdx, fromIdx);
0447 
0448         if (move.to < oldCount) {
0449             const QModelIndex &toIdx = index(move.to, 0);
0450             Q_EMIT dataChanged(toIdx, toIdx);
0451         }
0452     }
0453 
0454     if (newEnd < oldCount - 1) {
0455         endRemoveRows();
0456     } else if (newEnd > oldCount - 1) {
0457         endInsertRows();
0458         m_beginInsertRowsCalled = false;
0459     }
0460 
0461     m_folderModel->updateSelection(sourceRows, true);
0462 
0463     m_updatePositionsTimer->start();
0464 
0465     return toIndices.constFirst();
0466 }
0467 
0468 void Positioner::updatePositions()
0469 {
0470     QStringList positions;
0471 
0472     if (m_enabled && !m_proxyToSource.isEmpty() && m_perStripe > 0) {
0473         positions.append(QString::number((1 + ((rowCount() - 1) / m_perStripe))));
0474         positions.append(QString::number(m_perStripe));
0475 
0476         QHashIterator<int, int> it(m_proxyToSource);
0477 
0478         while (it.hasNext()) {
0479             it.next();
0480 
0481             const QString &name = m_folderModel->data(m_folderModel->index(it.value(), 0), FolderModel::UrlRole).toString();
0482 
0483             if (name.isEmpty()) {
0484                 return;
0485             }
0486 
0487             positions.append(name);
0488             positions.append(QString::number(qMax(0, it.key() / m_perStripe)));
0489             positions.append(QString::number(qMax(0, it.key() % m_perStripe)));
0490         }
0491     }
0492 
0493     if (positions != m_positions) {
0494         m_positions = positions;
0495 
0496         Q_EMIT positionsChanged();
0497     }
0498 }
0499 
0500 void Positioner::sourceStatusChanged()
0501 {
0502     if (m_deferApplyPositions && m_folderModel->status() != FolderModel::Listing) {
0503         applyPositions();
0504     }
0505 
0506     if (m_deferMovePositions.count() && m_folderModel->status() != FolderModel::Listing) {
0507         move(m_deferMovePositions);
0508         m_deferMovePositions.clear();
0509     }
0510 }
0511 
0512 void Positioner::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList<int> &roles)
0513 {
0514     if (m_enabled) {
0515         int start = topLeft.row();
0516         int end = bottomRight.row();
0517 
0518         for (int i = start; i <= end; ++i) {
0519             if (m_sourceToProxy.contains(i)) {
0520                 const QModelIndex &idx = index(m_sourceToProxy.value(i), 0);
0521 
0522                 Q_EMIT dataChanged(idx, idx);
0523             }
0524         }
0525     } else {
0526         Q_EMIT dataChanged(topLeft, bottomRight, roles);
0527     }
0528 }
0529 
0530 void Positioner::sourceModelAboutToBeReset()
0531 {
0532     beginResetModel();
0533 }
0534 
0535 void Positioner::sourceModelReset()
0536 {
0537     if (m_enabled) {
0538         initMaps();
0539     }
0540 
0541     endResetModel();
0542 }
0543 
0544 void Positioner::sourceRowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
0545 {
0546     if (m_enabled) {
0547         // Don't insert yet if we're waiting for listing to complete to apply
0548         // initial positions;
0549         if (m_deferApplyPositions) {
0550             return;
0551         } else if (m_proxyToSource.isEmpty()) {
0552             beginInsertRows(parent, start, end);
0553             m_beginInsertRowsCalled = true;
0554 
0555             initMaps(end + 1);
0556 
0557             return;
0558         }
0559 
0560         // When new rows are inserted, they might go in the beginning or in the middle.
0561         // In this case we must update first the existing proxy->source and source->proxy
0562         // mapping, otherwise the proxy items will point to the wrong source item.
0563         int count = end - start + 1;
0564         m_sourceToProxy.clear();
0565         for (auto it = m_proxyToSource.begin(); it != m_proxyToSource.end(); ++it) {
0566             int sourceIdx = *it;
0567             if (sourceIdx >= start) {
0568                 *it += count;
0569             }
0570             m_sourceToProxy[*it] = it.key();
0571         }
0572 
0573         int free = -1;
0574         int rest = -1;
0575 
0576         for (int i = start; i <= end; ++i) {
0577             free = firstFreeRow();
0578 
0579             if (free != -1) {
0580                 updateMaps(free, i);
0581                 m_pendingChanges << createIndex(free, 0);
0582             } else {
0583                 rest = i;
0584                 break;
0585             }
0586         }
0587 
0588         if (rest != -1) {
0589             int firstNew = lastRow() + 1;
0590             int remainder = (end - rest);
0591 
0592             beginInsertRows(parent, firstNew, firstNew + remainder);
0593             m_beginInsertRowsCalled = true;
0594 
0595             for (int i = 0; i <= remainder; ++i) {
0596                 updateMaps(firstNew + i, rest + i);
0597             }
0598         } else {
0599             m_ignoreNextTransaction = true;
0600         }
0601     } else {
0602         beginInsertRows(parent, start, end);
0603         beginInsertRows(parent, start, end);
0604         m_beginInsertRowsCalled = true;
0605     }
0606 }
0607 
0608 void Positioner::sourceRowsAboutToBeMoved(const QModelIndex &sourceParent,
0609                                           int sourceStart,
0610                                           int sourceEnd,
0611                                           const QModelIndex &destinationParent,
0612                                           int destinationRow)
0613 {
0614     beginMoveRows(sourceParent, sourceStart, sourceEnd, destinationParent, destinationRow);
0615 }
0616 
0617 void Positioner::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
0618 {
0619     if (m_enabled) {
0620         int oldLast = lastRow();
0621 
0622         for (int i = first; i <= last; ++i) {
0623             int proxyRow = m_sourceToProxy.take(i);
0624             m_proxyToSource.remove(proxyRow);
0625             m_pendingChanges << createIndex(proxyRow, 0);
0626         }
0627 
0628         QHash<int, int> newProxyToSource;
0629         QHash<int, int> newSourceToProxy;
0630         QHashIterator<int, int> it(m_sourceToProxy);
0631         int delta = std::abs(first - last) + 1;
0632 
0633         while (it.hasNext()) {
0634             it.next();
0635 
0636             if (it.key() > last) {
0637                 newProxyToSource.insert(it.value(), it.key() - delta);
0638                 newSourceToProxy.insert(it.key() - delta, it.value());
0639             } else {
0640                 newProxyToSource.insert(it.value(), it.key());
0641                 newSourceToProxy.insert(it.key(), it.value());
0642             }
0643         }
0644 
0645         m_proxyToSource = newProxyToSource;
0646         m_sourceToProxy = newSourceToProxy;
0647 
0648         int newLast = lastRow();
0649 
0650         if (oldLast > newLast) {
0651             int diff = oldLast - newLast;
0652             beginRemoveRows(QModelIndex(), ((oldLast - diff) + 1), oldLast);
0653         } else {
0654             m_ignoreNextTransaction = true;
0655         }
0656     } else {
0657         beginRemoveRows(parent, first, last);
0658     }
0659 }
0660 
0661 void Positioner::sourceLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
0662 {
0663     Q_UNUSED(parents)
0664 
0665     Q_EMIT layoutAboutToBeChanged(QList<QPersistentModelIndex>(), hint);
0666 }
0667 
0668 void Positioner::sourceRowsInserted(const QModelIndex &parent, int first, int last)
0669 {
0670     Q_UNUSED(parent)
0671     Q_UNUSED(first)
0672     Q_UNUSED(last)
0673 
0674     if (!m_ignoreNextTransaction) {
0675         if (m_beginInsertRowsCalled) {
0676             endInsertRows();
0677             m_beginInsertRowsCalled = false;
0678         }
0679     } else {
0680         m_ignoreNextTransaction = false;
0681     }
0682 
0683     flushPendingChanges();
0684 
0685     // Don't generate new positions data if we're waiting for listing to
0686     // complete to apply initial positions.
0687     if (!m_deferApplyPositions) {
0688         m_updatePositionsTimer->start();
0689     }
0690 }
0691 
0692 void Positioner::sourceRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow)
0693 {
0694     Q_UNUSED(sourceParent)
0695     Q_UNUSED(sourceStart)
0696     Q_UNUSED(sourceEnd)
0697     Q_UNUSED(destinationParent)
0698     Q_UNUSED(destinationRow)
0699 
0700     endMoveRows();
0701 }
0702 
0703 void Positioner::sourceRowsRemoved(const QModelIndex &parent, int first, int last)
0704 {
0705     Q_UNUSED(parent)
0706     Q_UNUSED(first)
0707     Q_UNUSED(last)
0708 
0709     if (!m_ignoreNextTransaction) {
0710         Q_EMIT endRemoveRows();
0711     } else {
0712         m_ignoreNextTransaction = false;
0713     }
0714 
0715     flushPendingChanges();
0716 
0717     m_updatePositionsTimer->start();
0718 }
0719 
0720 void Positioner::sourceLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
0721 {
0722     Q_UNUSED(parents)
0723 
0724     if (m_enabled) {
0725         initMaps();
0726     }
0727 
0728     Q_EMIT layoutChanged(QList<QPersistentModelIndex>(), hint);
0729 }
0730 
0731 void Positioner::initMaps(int size)
0732 {
0733     m_proxyToSource.clear();
0734     m_sourceToProxy.clear();
0735 
0736     if (size == -1) {
0737         size = m_folderModel->rowCount();
0738     }
0739 
0740     if (!size) {
0741         return;
0742     }
0743 
0744     for (int i = 0; i < size; ++i) {
0745         updateMaps(i, i);
0746     }
0747 }
0748 
0749 void Positioner::updateMaps(int proxyIndex, int sourceIndex)
0750 {
0751     m_proxyToSource.insert(proxyIndex, sourceIndex);
0752     m_sourceToProxy.insert(sourceIndex, proxyIndex);
0753 }
0754 
0755 int Positioner::firstRow() const
0756 {
0757     if (!m_proxyToSource.isEmpty()) {
0758         QList<int> keys(m_proxyToSource.keys());
0759         std::sort(keys.begin(), keys.end());
0760 
0761         return keys.first();
0762     }
0763 
0764     return -1;
0765 }
0766 
0767 int Positioner::lastRow() const
0768 {
0769     if (!m_proxyToSource.isEmpty()) {
0770         QList<int> keys(m_proxyToSource.keys());
0771         std::sort(keys.begin(), keys.end());
0772         return keys.last();
0773     }
0774 
0775     return 0;
0776 }
0777 
0778 int Positioner::firstFreeRow() const
0779 {
0780     if (!m_proxyToSource.isEmpty()) {
0781         int last = lastRow();
0782 
0783         for (int i = 0; i <= last; ++i) {
0784             if (!m_proxyToSource.contains(i)) {
0785                 return i;
0786             }
0787         }
0788     }
0789 
0790     return -1;
0791 }
0792 
0793 void Positioner::applyPositions()
0794 {
0795     // We were called while the source model is listing. Defer applying positions
0796     // until listing completes.
0797     if (m_folderModel->status() == FolderModel::Listing) {
0798         m_deferApplyPositions = true;
0799 
0800         return;
0801     }
0802 
0803     if (m_positions.size() < 5) {
0804         // We were waiting for listing to complete before proxying source rows,
0805         // but we don't have positions to apply. Reset to populate.
0806         if (m_deferApplyPositions) {
0807             m_deferApplyPositions = false;
0808             reset();
0809         }
0810 
0811         return;
0812     }
0813 
0814     beginResetModel();
0815 
0816     m_proxyToSource.clear();
0817     m_sourceToProxy.clear();
0818 
0819     // The assertion makes sure std::span is faster than QList::mid
0820     static_assert(!std::is_trivially_copy_assignable_v<decltype(m_positions)::value_type>);
0821     const std::span positions{std::next(m_positions.cbegin(), 2), m_positions.cend()};
0822 
0823     if (positions.size() % 3 != 0) {
0824         return;
0825     }
0826 
0827     QHash<QString, int> sourceIndices;
0828 
0829     for (int i = 0; i < m_folderModel->rowCount(); ++i) {
0830         sourceIndices.insert(m_folderModel->data(m_folderModel->index(i, 0), FolderModel::UrlRole).toString(), i);
0831     }
0832 
0833     QString name;
0834     int stripe = -1;
0835     int pos = -1;
0836     int sourceIndex = -1;
0837     int index = -1;
0838     bool ok = false;
0839     int offset = 0;
0840 
0841     // Restore positions for items that still fit.
0842     for (std::size_t i = 0; i < positions.size() / 3; ++i) {
0843         offset = i * 3;
0844         pos = positions[offset + 2].toInt(&ok);
0845         if (!ok) {
0846             return;
0847         }
0848 
0849         if (pos <= m_perStripe) {
0850             name = positions[offset];
0851             stripe = positions[offset + 1].toInt(&ok);
0852             if (!ok) {
0853                 return;
0854             }
0855 
0856             if (!sourceIndices.contains(name)) {
0857                 continue;
0858             } else {
0859                 sourceIndex = sourceIndices.value(name);
0860             }
0861 
0862             index = (stripe * m_perStripe) + pos;
0863 
0864             if (m_proxyToSource.contains(index)) {
0865                 continue;
0866             }
0867 
0868             updateMaps(index, sourceIndex);
0869             sourceIndices.remove(name);
0870         }
0871     }
0872 
0873     // Find new positions for items that didn't fit.
0874     for (std::size_t i = 0; i < positions.size() / 3; ++i) {
0875         offset = i * 3;
0876         pos = positions[offset + 2].toInt(&ok);
0877         if (!ok) {
0878             return;
0879         }
0880 
0881         if (pos > m_perStripe) {
0882             name = positions[offset];
0883 
0884             if (!sourceIndices.contains(name)) {
0885                 continue;
0886             } else {
0887                 sourceIndex = sourceIndices.take(name);
0888             }
0889 
0890             index = firstFreeRow();
0891 
0892             if (index == -1) {
0893                 index = lastRow() + 1;
0894             }
0895 
0896             updateMaps(index, sourceIndex);
0897         }
0898     }
0899 
0900     QHashIterator<QString, int> it(sourceIndices);
0901 
0902     // Find positions for new source items we don't have records for.
0903     while (it.hasNext()) {
0904         it.next();
0905 
0906         index = firstFreeRow();
0907 
0908         if (index == -1) {
0909             index = lastRow() + 1;
0910         }
0911 
0912         updateMaps(index, it.value());
0913     }
0914 
0915     endResetModel();
0916 
0917     m_deferApplyPositions = false;
0918 
0919     m_updatePositionsTimer->start();
0920 }
0921 
0922 void Positioner::flushPendingChanges()
0923 {
0924     if (m_pendingChanges.isEmpty()) {
0925         return;
0926     }
0927 
0928     int last = lastRow();
0929 
0930     for (const QModelIndex &index : std::as_const(m_pendingChanges)) {
0931         if (index.row() <= last) {
0932             Q_EMIT dataChanged(index, index);
0933         }
0934     }
0935 
0936     m_pendingChanges.clear();
0937 }
0938 
0939 void Positioner::connectSignals(FolderModel *model)
0940 {
0941     connect(model, &QAbstractItemModel::dataChanged, this, &Positioner::sourceDataChanged, Qt::UniqueConnection);
0942     connect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, &Positioner::sourceRowsAboutToBeInserted, Qt::UniqueConnection);
0943     connect(model, &QAbstractItemModel::rowsAboutToBeMoved, this, &Positioner::sourceRowsAboutToBeMoved, Qt::UniqueConnection);
0944     connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &Positioner::sourceRowsAboutToBeRemoved, Qt::UniqueConnection);
0945     connect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, &Positioner::sourceLayoutAboutToBeChanged, Qt::UniqueConnection);
0946     connect(model, &QAbstractItemModel::rowsInserted, this, &Positioner::sourceRowsInserted, Qt::UniqueConnection);
0947     connect(model, &QAbstractItemModel::rowsMoved, this, &Positioner::sourceRowsMoved, Qt::UniqueConnection);
0948     connect(model, &QAbstractItemModel::rowsRemoved, this, &Positioner::sourceRowsRemoved, Qt::UniqueConnection);
0949     connect(model, &QAbstractItemModel::layoutChanged, this, &Positioner::sourceLayoutChanged, Qt::UniqueConnection);
0950     connect(m_folderModel, &FolderModel::urlChanged, this, &Positioner::reset, Qt::UniqueConnection);
0951     connect(m_folderModel, &FolderModel::statusChanged, this, &Positioner::sourceStatusChanged, Qt::UniqueConnection);
0952 }
0953 
0954 void Positioner::disconnectSignals(FolderModel *model)
0955 {
0956     disconnect(model, &QAbstractItemModel::dataChanged, this, &Positioner::sourceDataChanged);
0957     disconnect(model, &QAbstractItemModel::rowsAboutToBeInserted, this, &Positioner::sourceRowsAboutToBeInserted);
0958     disconnect(model, &QAbstractItemModel::rowsAboutToBeMoved, this, &Positioner::sourceRowsAboutToBeMoved);
0959     disconnect(model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &Positioner::sourceRowsAboutToBeRemoved);
0960     disconnect(model, &QAbstractItemModel::layoutAboutToBeChanged, this, &Positioner::sourceLayoutAboutToBeChanged);
0961     disconnect(model, &QAbstractItemModel::rowsInserted, this, &Positioner::sourceRowsInserted);
0962     disconnect(model, &QAbstractItemModel::rowsMoved, this, &Positioner::sourceRowsMoved);
0963     disconnect(model, &QAbstractItemModel::rowsRemoved, this, &Positioner::sourceRowsRemoved);
0964     disconnect(model, &QAbstractItemModel::layoutChanged, this, &Positioner::sourceLayoutChanged);
0965     disconnect(m_folderModel, &FolderModel::urlChanged, this, &Positioner::reset);
0966     disconnect(m_folderModel, &FolderModel::statusChanged, this, &Positioner::sourceStatusChanged);
0967 }