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 }