File indexing completed on 2024-05-12 04:43:03
0001 /*************************************************************************** 0002 * Copyright (C) 2017-2018 by Emmanuel Lepage Vallee * 0003 * Author : Emmanuel Lepage Vallee <emmanuel.lepage@kde.org> * 0004 * * 0005 * This program is free software; you can redistribute it and/or modify * 0006 * it under the terms of the GNU General Public License as published by * 0007 * the Free Software Foundation; either version 3 of the License, or * 0008 * (at your option) any later version. * 0009 * * 0010 * This program is distributed in the hope that it will be useful, * 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0013 * GNU General Public License for more details. * 0014 * * 0015 * You should have received a copy of the GNU General Public License * 0016 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0017 **************************************************************************/ 0018 #include "content_p.h" 0019 0020 // KQuickItemViews 0021 #include "viewitem_p.h" 0022 #include "proximity_p.h" 0023 #include "model_p.h" 0024 #include "modelitem_p.h" 0025 #include <viewport.h> 0026 #include <private/viewport_p.h> 0027 #include <private/indexmetadata_p.h> 0028 #include <private/statetracker/continuity_p.h> 0029 #include <adapters/contextadapter.h> 0030 0031 // Qt 0032 #include <QtCore/QDebug> 0033 0034 using EdgeType = IndexMetadata::EdgeType; 0035 0036 namespace StateTracker { 0037 class Index; 0038 class Model; 0039 class Content; 0040 } 0041 0042 class AbstractItemAdapter; 0043 class ModelRect; 0044 #include <private/indexmetadata_p.h> 0045 #include <private/statetracker/modelitem_p.h> 0046 0047 class ContentPrivate : public QObject 0048 { 0049 Q_OBJECT 0050 public: 0051 explicit ContentPrivate(); 0052 0053 // Helpers 0054 StateTracker::ModelItem* addChildren(const QModelIndex& index); 0055 StateTracker::ModelItem* ttiForIndex(const QModelIndex& idx) const; 0056 0057 bool isInsertActive(const QModelIndex& p, int first, int last) const; 0058 0059 QList<StateTracker::Index*> setTemporaryIndices(const QModelIndex &parent, int start, int end, 0060 const QModelIndex &destination, int row); 0061 void resetTemporaryIndices(const QList<StateTracker::Index*>&); 0062 0063 void reloadEdges(); 0064 0065 StateTracker::ModelItem *m_pRoot {nullptr}; 0066 0067 //TODO add a circular buffer to GC the items 0068 // * relative index: when to trigger GC 0069 // * absolute array index: kept in the StateTracker::ModelItem so it can 0070 // remove itself 0071 0072 /// All elements with loaded children 0073 QHash<QPersistentModelIndex, StateTracker::ModelItem*> m_hMapper; 0074 0075 QModelIndex getNextIndex(const QModelIndex& idx) const; 0076 0077 ModelRect m_lRects[3]; 0078 StateTracker::Model *m_pModelTracker; 0079 Viewport *m_pViewport; 0080 0081 StateTracker::Content* q_ptr; 0082 0083 // Update the ModelRect 0084 void insertEdge(IndexMetadata *, StateTracker::ModelItem::State); 0085 void removeEdge(IndexMetadata *, StateTracker::ModelItem::State); 0086 void enterState(IndexMetadata *, StateTracker::ModelItem::State); 0087 void leaveState(IndexMetadata *, StateTracker::ModelItem::State); 0088 void error (IndexMetadata *, StateTracker::ModelItem::State); 0089 void resetState(IndexMetadata *, StateTracker::ModelItem::State); 0090 0091 typedef void(ContentPrivate::*StateFS)(IndexMetadata*, StateTracker::ModelItem::State); 0092 static const StateFS m_fStateLogging[8][2]; 0093 0094 public Q_SLOTS: 0095 void slotCleanup(); 0096 void slotRowsInserted (const QModelIndex& parent, int first, int last); 0097 void slotRowsRemoved (const QModelIndex& parent, int first, int last); 0098 void slotLayoutChanged ( ); 0099 void slotDataChanged (const QModelIndex& tl, const QModelIndex& br, 0100 const QVector<int> &roles ); 0101 void slotRowsMoved (const QModelIndex &p, int start, int end, 0102 const QModelIndex &dest, int row); 0103 }; 0104 0105 #define A &ContentPrivate:: 0106 // Keep track of the number of instances per state 0107 const ContentPrivate::StateFS ContentPrivate::m_fStateLogging[8][2] = { 0108 /* ENTER LEAVE */ 0109 /*NEW */ { A error , A leaveState }, 0110 /*BUFFER */ { A enterState, A leaveState }, 0111 /*REMOVED */ { A enterState, A leaveState }, 0112 /*REACHABLE*/ { A enterState, A leaveState }, 0113 /*VISIBLE */ { A insertEdge, A removeEdge }, 0114 /*ERROR */ { A enterState, A leaveState }, 0115 /*DANGLING */ { A enterState, A error }, 0116 /*MOVING */ { A enterState, A resetState }, 0117 }; 0118 #undef A 0119 0120 void ContentPrivate::insertEdge(IndexMetadata *md, StateTracker::ModelItem::State s) 0121 { 0122 auto tti = md->modelTracker(); 0123 Q_UNUSED(s) 0124 const auto first = q_ptr->edges(EdgeType::VISIBLE)->getEdge( Qt::TopEdge ); 0125 const auto last = q_ptr->edges(EdgeType::VISIBLE)->getEdge( Qt::BottomEdge ); 0126 0127 const auto prev = tti->up(); 0128 const auto next = tti->down(); 0129 0130 if (first == next) 0131 q_ptr->setEdge(EdgeType::VISIBLE, tti, Qt::TopEdge); 0132 0133 if (last == prev) 0134 q_ptr->setEdge(EdgeType::VISIBLE, tti, Qt::BottomEdge); 0135 0136 _DO_TEST(_test_validate_edges_simple, q_ptr) 0137 } 0138 0139 void ContentPrivate::removeEdge(IndexMetadata *md, StateTracker::ModelItem::State s) 0140 { 0141 Q_UNUSED(s) 0142 auto tti = md->modelTracker(); 0143 const auto first = q_ptr->edges(EdgeType::VISIBLE)->getEdge(Qt::TopEdge); 0144 const auto last = q_ptr->edges(EdgeType::VISIBLE)->getEdge(Qt::BottomEdge); 0145 0146 // The item was somewhere in between, not an edge case 0147 if ((tti != first) && (tti != last)) 0148 return; 0149 0150 auto prev = tti->up(); 0151 auto next = tti->down(); 0152 0153 if (tti == first) 0154 q_ptr->setEdge(EdgeType::VISIBLE, 0155 (next && next->metadata()->modelTracker()->state() == StateTracker::ModelItem::State::VISIBLE) ? 0156 next : nullptr, 0157 Qt::TopEdge 0158 ); 0159 0160 if (tti == last) 0161 q_ptr->setEdge(EdgeType::VISIBLE, 0162 (prev && prev->metadata()->modelTracker()->state() == StateTracker::ModelItem::State::VISIBLE) ? 0163 prev : nullptr, 0164 Qt::BottomEdge 0165 ); 0166 0167 _DO_TEST(_test_validate_edges_simple, q_ptr) 0168 } 0169 0170 /** 0171 * This method is called when an item moves into a new area and the "real" 0172 * new state needs to be determined. 0173 */ 0174 void ContentPrivate::resetState(IndexMetadata *i, StateTracker::ModelItem::State) 0175 { 0176 i->modelTracker()->rebuildState(); 0177 } 0178 0179 ContentPrivate::ContentPrivate() 0180 {} 0181 0182 StateTracker::Content::Content(Viewport* parent) : QObject(parent), 0183 d_ptr(new ContentPrivate()) 0184 { 0185 d_ptr->m_pViewport = parent; 0186 d_ptr->m_pRoot = new StateTracker::ModelItem(parent); 0187 d_ptr->m_pModelTracker = new StateTracker::Model(this); 0188 d_ptr->q_ptr = this; 0189 } 0190 0191 StateTracker::Content::~Content() 0192 { 0193 delete d_ptr->m_pModelTracker; 0194 d_ptr->m_pModelTracker = nullptr; 0195 delete d_ptr->m_pRoot; 0196 } 0197 0198 void ContentPrivate::slotRowsInserted(const QModelIndex& parent, int first, int last) 0199 { 0200 // This method uses modelCandidate() because it needs to be called during 0201 // initilization (thus not always directly by QAbstractItemModel::rowsInserted 0202 0203 Q_ASSERT(((!parent.isValid()) || parent.model() == m_pModelTracker->modelCandidate()) && first <= last); 0204 0205 // It is the job of isInsertActive to decide what's correct 0206 if (!isInsertActive(parent, first, last)) { 0207 _DO_TEST(_test_validateLinkedList, q_ptr) 0208 return; 0209 } 0210 0211 const auto pitem = parent.isValid() ? m_hMapper.value(parent) : m_pRoot; 0212 0213 //FIXME it is possible if the anchor is at the bottom that the parent 0214 // needs to be loaded. But this is currently too not supported. 0215 if (!pitem) { 0216 _DO_TEST(_test_validateLinkedList, q_ptr) 0217 return; 0218 } 0219 0220 StateTracker::Index *prev = nullptr; 0221 0222 //FIXME use up() 0223 if (first && pitem) 0224 prev = pitem->childrenLookup(m_pModelTracker->modelCandidate()->index(first-1, 0, parent)); 0225 0226 // There is no choice here but to load a larger subset to avoid holes, in 0227 // theory, edges(EdgeType::FREE)->m_Edges will prevent runaway loading 0228 if (pitem->firstChild()) 0229 last = std::max(last, pitem->firstChild()->effectiveRow() - 1); 0230 0231 if (prev && prev->down()) { 0232 m_pViewport->s_ptr->notifyInsert(prev->down()->metadata()); 0233 //Q_ASSERT(!TTI(prev->down())->metadata()->isValid()); 0234 } 0235 else if ((!prev) && (!pitem) && m_pRoot->firstChild()) { 0236 Q_ASSERT((!first) && !parent.isValid()); 0237 m_pViewport->s_ptr->notifyInsert(m_pRoot->firstChild()->metadata()); 0238 //Q_ASSERT(!TTI(m_pRoot->firstChild())->metadata()->isValid()); 0239 } 0240 else if (pitem && pitem != m_pRoot && pitem->down()) { 0241 //Q_ASSERT(!first); 0242 m_pViewport->s_ptr->notifyInsert(pitem->down()->metadata()); 0243 0244 //TODO check if this one is still correct, right now notifyInsert update the geo 0245 //Q_ASSERT(!TTI(pitem->down())->metadata()->isValid()); 0246 } 0247 0248 //FIXME support smaller ranges 0249 for (int i = first; i <= last; i++) { 0250 auto idx = m_pModelTracker->modelCandidate()->index(i, 0, parent); 0251 Q_ASSERT(idx.isValid() && idx.parent() != idx && idx.model() == m_pModelTracker->modelCandidate()); 0252 0253 // If the insertion is sandwiched between loaded items, not doing it 0254 // will corrupt the view, but if it's a "tail" insertion, then they 0255 // can be discarded. 0256 if (!(q_ptr->edges(EdgeType::FREE)->m_Edges & (Qt::BottomEdge | Qt::TopEdge))) { 0257 const auto nextIdx = getNextIndex(idx); 0258 const auto nextTTI = nextIdx.isValid() ? ttiForIndex(nextIdx) : nullptr; 0259 0260 if (!nextTTI) { 0261 m_pViewport->s_ptr->refreshVisible(); 0262 _DO_TEST(_test_validateLinkedList, q_ptr) 0263 return; //FIXME break 0264 } 0265 } 0266 0267 auto e = addChildren(idx); 0268 0269 if (pitem->firstChild() && pitem->firstChild()->effectiveRow() == idx.row()+1) { 0270 Q_ASSERT(idx.parent() == pitem->firstChild()->effectiveParentIndex()); 0271 0272 // m_pViewport->s_ptr->notifyInsert(&TTI(pitem->firstChild())->m_Geometry); 0273 StateTracker::Index::insertChildBefore(e, pitem->firstChild(), pitem); 0274 } 0275 else { 0276 StateTracker::Index::insertChildAfter(e, prev, pitem); //FIXME incorrect 0277 // m_pViewport->s_ptr->notifyInsert(&TTI(prev)->m_Geometry); 0278 } 0279 0280 //FIXME It can happen if the previous is out of the visible range 0281 Q_ASSERT( e->previousSibling() || e->nextSibling() || e->effectiveRow() == 0); 0282 0283 //TODO merge with bridgeGap 0284 if (prev) { 0285 StateTracker::Index::bridgeGap(prev, e); 0286 _DO_TEST_IDX(_test_validate_chain, prev->parent()) 0287 } 0288 0289 // This is required before ::ATTACH because otherwise ::down() wont work 0290 0291 Q_ASSERT((!pitem->firstChild()) || !pitem->firstChild()->hasTemporaryIndex()); //TODO 0292 0293 const bool needDownMove = (!pitem->lastChild()) || 0294 e->effectiveRow() > pitem->lastChild()->effectiveRow(); 0295 0296 const bool needUpMove = (!pitem->firstChild()) || 0297 e->effectiveRow() < pitem->firstChild()->effectiveRow(); 0298 0299 _DO_TEST_IDX(_test_validate_chain, e->parent()) 0300 0301 // The item is about the current parent first item 0302 if (needUpMove) { 0303 // Q_ASSERT(false); //TODO merge with the other bridgeGap above 0304 0305 m_pViewport->s_ptr->notifyInsert(m_pRoot->firstChild()->metadata()); 0306 Q_ASSERT((!m_pRoot->firstChild()) || !m_pRoot->firstChild()->metadata()->isValid()); 0307 StateTracker::Index::insertChildBefore(e, nullptr, pitem); 0308 } 0309 0310 _DO_TEST_IDX(_test_validate_chain, e->parent()) 0311 0312 Q_ASSERT(e->metadata()->geometryTracker()->state() == StateTracker::Geometry::State::INIT); 0313 Q_ASSERT(e->state() != StateTracker::ModelItem::State::VISIBLE); 0314 0315 // NEW -> REACHABLE, this should never fail 0316 if (!e->metadata()->performAction(IndexMetadata::LoadAction::ATTACH)) { 0317 qDebug() << "\n\nATTACH FAILED"; 0318 _DO_TEST(_test_validateLinkedList, q_ptr) 0319 break; 0320 } 0321 0322 if (needUpMove) { 0323 Q_ASSERT(pitem != e); 0324 if (auto pe = e->up()) 0325 pe->metadata() << IndexMetadata::LoadAction::MOVE; 0326 } 0327 0328 Q_ASSERT((!pitem->lastChild()) || !pitem->lastChild()->hasTemporaryIndex()); //TODO 0329 0330 if (needDownMove) { 0331 if (auto ne = e->down()) { 0332 Q_ASSERT(!ne->metadata()->isValid()); 0333 0334 ne->metadata() << IndexMetadata::LoadAction::MOVE; 0335 Q_ASSERT(!ne->metadata()->isValid()); 0336 } 0337 } 0338 0339 int rc = m_pModelTracker->modelCandidate()->rowCount(idx); 0340 if (rc && q_ptr->edges(EdgeType::FREE)->m_Edges & Qt::BottomEdge) { 0341 slotRowsInserted(idx, 0, rc-1); 0342 } 0343 0344 // Validate early to prevent propagating garbage that's nearly impossible 0345 // to debug. 0346 if (pitem && pitem != m_pRoot && !i) { 0347 Q_ASSERT(e->up() == pitem); 0348 Q_ASSERT(e == pitem->down()); 0349 } 0350 0351 prev = e; 0352 } 0353 0354 m_pViewport->s_ptr->refreshVisible(); 0355 0356 _DO_TEST(_test_validateLinkedList, q_ptr) 0357 _DO_TEST_IDX(_test_validate_chain, pitem) 0358 0359 Q_EMIT q_ptr->contentChanged(); 0360 } 0361 0362 void ContentPrivate::slotRowsRemoved(const QModelIndex& parent, int first, int last) 0363 { 0364 Q_ASSERT((!parent.isValid()) || parent.model() == m_pModelTracker->modelCandidate()); 0365 0366 if (!q_ptr->isActive(parent, first, last)) { 0367 _DO_TEST(_test_validateLinkedList, q_ptr) 0368 _DO_TEST(_test_validateUnloaded, q_ptr, parent, first, last) 0369 return; 0370 } 0371 0372 auto pitem = parent.isValid() ? m_hMapper.value(parent) : m_pRoot; 0373 0374 if (!pitem) 0375 return; 0376 0377 //TODO make sure the state machine support them 0378 //StateTracker::ModelItem *prev(nullptr), *next(nullptr); 0379 0380 //FIXME use up() 0381 //if (first && pitem) 0382 // prev = pitem->m_hLookup.value(model()->index(first-1, 0, parent)); 0383 0384 //next = pitem->m_hLookup.value(model()->index(last+1, 0, parent)); 0385 0386 //FIXME support smaller ranges 0387 for (int i = first; i <= last; i++) { 0388 auto idx = m_pModelTracker->modelCandidate()->index(i, 0, parent); 0389 0390 if (auto elem = pitem->childrenLookup(idx)) { 0391 elem->metadata() 0392 << IndexMetadata::LoadAction::HIDE 0393 << IndexMetadata::LoadAction::DETACH; 0394 } 0395 } 0396 0397 Q_EMIT q_ptr->contentChanged(); 0398 } 0399 0400 //TODO optimize this 0401 void ContentPrivate::slotDataChanged(const QModelIndex& tl, const QModelIndex& br, const QVector<int> &roles) 0402 { 0403 Q_ASSERT(((!tl.isValid()) || tl.model() == m_pModelTracker->modelCandidate())); 0404 Q_ASSERT(((!br.isValid()) || br.model() == m_pModelTracker->modelCandidate())); 0405 0406 if (!q_ptr->isActive(tl.parent(), tl.row(), br.row())) 0407 return; 0408 0409 for (int i = tl.row(); i <= br.row(); i++) { 0410 const auto idx = m_pModelTracker->modelCandidate()->index(i, tl.column(), tl.parent()); 0411 if (auto tti = ttiForIndex(idx)) { 0412 if (tti->metadata()->viewTracker()) { 0413 tti->metadata()->contextAdapter()->updateRoles(roles); 0414 tti->metadata() << IndexMetadata::ViewAction::UPDATE; 0415 } 0416 } 0417 } 0418 } 0419 0420 void ContentPrivate::slotLayoutChanged() 0421 { 0422 if (auto rc = m_pModelTracker->modelCandidate()->rowCount()) 0423 slotRowsInserted({}, 0, rc - 1); 0424 0425 Q_EMIT q_ptr->contentChanged(); 0426 } 0427 0428 void ContentPrivate::slotRowsMoved(const QModelIndex &parent, int start, int end, 0429 const QModelIndex &destination, int row) 0430 { 0431 Q_ASSERT((!parent.isValid()) || parent.model() == m_pModelTracker->modelCandidate()); 0432 Q_ASSERT((!destination.isValid()) || destination.model() == m_pModelTracker->modelCandidate()); 0433 0434 // There is literally nothing to do 0435 if (parent == destination && start == row) 0436 return; 0437 0438 // Whatever has to be done only affect a part that's not currently tracked. 0439 if (!q_ptr->isActive(destination, row, row)) { 0440 //Q_ASSERT(false); //TODO so I don't forget 0441 return; 0442 } 0443 0444 auto tmp = setTemporaryIndices(parent, start, end, destination, row); 0445 0446 // As the actual view is implemented as a daisy chained list, only moving 0447 // the edges is necessary for the StateTracker::ModelItem. Each StateTracker::ViewItem 0448 // need to be moved. 0449 0450 const auto idxStart = m_pModelTracker->modelCandidate()->index(start, 0, parent); 0451 const auto idxEnd = m_pModelTracker->modelCandidate()->index(end , 0, parent); 0452 Q_ASSERT(idxStart.isValid() && idxEnd.isValid()); 0453 0454 //FIXME once partial ranges are supported, this is no longer always valid 0455 auto startTTI = ttiForIndex(idxStart); 0456 auto endTTI = ttiForIndex(idxEnd); 0457 0458 if (end - start == 1) 0459 Q_ASSERT(startTTI->nextSibling() == endTTI); 0460 0461 0462 //FIXME so I don't forget, it will mess things up if silently ignored 0463 Q_ASSERT(startTTI->parent() == endTTI->parent()); //TODO not implemented 0464 Q_ASSERT(startTTI && endTTI); //TODO partially loaded move are not implemented 0465 0466 auto oldPreviousTTI = startTTI->up(); 0467 auto oldNextTTI = endTTI->down(); 0468 0469 Q_ASSERT((!oldPreviousTTI) || oldPreviousTTI->down() == startTTI); 0470 Q_ASSERT((!oldNextTTI) || oldNextTTI->up() == endTTI); 0471 0472 auto newNextIdx = m_pModelTracker->modelCandidate()->index(row, 0, destination); 0473 0474 // You cannot move things into an empty model 0475 Q_ASSERT((!row) || newNextIdx.isValid()); 0476 0477 StateTracker::Index *newNextTTI(nullptr), *newPrevTTI(nullptr); 0478 0479 // Rewind until a next element is found, this happens when destination is empty 0480 if (!newNextIdx.isValid() && destination.parent().isValid()) { 0481 Q_ASSERT(m_pModelTracker->modelCandidate()->rowCount(destination) == row); 0482 auto par = destination.parent(); 0483 do { 0484 if (m_pModelTracker->modelCandidate()->rowCount(par.parent()) > par.row()) { 0485 newNextIdx = m_pModelTracker->modelCandidate()->index(par.row(), 0, par.parent()); 0486 break; 0487 } 0488 0489 par = par.parent(); 0490 } while (par.isValid()); 0491 0492 newNextTTI = ttiForIndex(newNextIdx); 0493 } 0494 else { 0495 newNextTTI = ttiForIndex(newNextIdx); 0496 newPrevTTI = newNextTTI ? newNextTTI->up() : nullptr; 0497 } 0498 0499 if (!row) { 0500 auto otherI = ttiForIndex(destination); 0501 Q_ASSERT((!newPrevTTI) || otherI == newPrevTTI); 0502 } 0503 0504 // When there is no next element, then the parent has to be extracted manually 0505 if (!(newNextTTI || newPrevTTI)) { 0506 if (!row) 0507 newPrevTTI = ttiForIndex(destination); 0508 else { 0509 newPrevTTI = ttiForIndex( 0510 destination.model()->index(row-1, 0, destination) 0511 ); 0512 } 0513 } 0514 0515 StateTracker::ModelItem* newParentTTI = ttiForIndex(destination); 0516 Q_ASSERT(newParentTTI || !destination.isValid()); //TODO not coded yet 0517 0518 newParentTTI = newParentTTI ? newParentTTI : m_pRoot; 0519 0520 // Remove everything //TODO add batching again 0521 StateTracker::Index* tti = endTTI; 0522 StateTracker::Index* dest = newNextTTI && newNextTTI->parent() == newParentTTI ? 0523 newNextTTI : nullptr; 0524 0525 QList<StateTracker::Index*> tmp2; 0526 do { 0527 tmp2 << tti; 0528 } while(endTTI != startTTI && (tti = tti->previousSibling()) != startTTI); 0529 0530 if (endTTI != startTTI) //FIXME use a better condition 0531 tmp2 << startTTI; 0532 0533 Q_ASSERT( (end - start) == tmp2.size() - 1); 0534 0535 // Check if the point where 0536 //TODO this isn't good enough 0537 bool isRangeVisible = (newPrevTTI && 0538 newPrevTTI->metadata()->modelTracker()->state() == StateTracker::ModelItem::State::VISIBLE) || (newNextTTI && 0539 newNextTTI->metadata()->modelTracker()->state() == StateTracker::ModelItem::State::VISIBLE); 0540 0541 const auto topEdge = q_ptr->edges(EdgeType::VISIBLE)->getEdge(Qt::TopEdge); 0542 const auto bottomEdge = q_ptr->edges(EdgeType::VISIBLE)->getEdge(Qt::TopEdge); 0543 0544 bool needRefreshVisibleTop = false; 0545 bool needRefreshVisibleBottom = false; 0546 /*bool needRefreshBufferTop = false; //TODO lateral move is not implemented 0547 bool needRefreshBufferBottom = false;*/ 0548 0549 for (auto item : qAsConst(tmp2)) { 0550 needRefreshVisibleTop |= item != topEdge; 0551 needRefreshVisibleBottom |= item != bottomEdge; 0552 0553 item->metadata() << IndexMetadata::GeometryAction::MOVE; 0554 0555 item->remove(); 0556 0557 StateTracker::Index::insertChildBefore(item, dest, newParentTTI); 0558 0559 Q_ASSERT((!dest) || item->down() == dest); 0560 0561 if (dest) 0562 dest->metadata() << IndexMetadata::GeometryAction::MOVE; 0563 0564 _DO_TEST(_test_validate_edges_simple, q_ptr) 0565 m_pViewport->s_ptr->notifyInsert(item->metadata()); 0566 0567 dest = item; 0568 if (isRangeVisible) { 0569 if (item->metadata()->modelTracker()->state() == StateTracker::ModelItem::State::BUFFER) 0570 item->metadata() << IndexMetadata::LoadAction::ATTACH; 0571 0572 item->metadata() << IndexMetadata::LoadAction::SHOW; 0573 } 0574 } 0575 0576 _DO_TEST(_test_validate_move, q_ptr, newParentTTI, startTTI, endTTI, newPrevTTI, newNextTTI, row) 0577 0578 resetTemporaryIndices(tmp); 0579 0580 // The visible edges are now probably incorectly placed, reload them 0581 if (needRefreshVisibleTop || needRefreshVisibleBottom) { 0582 reloadEdges(); 0583 m_pViewport->s_ptr->refreshVisible(); 0584 } 0585 0586 //WARNING The indices still are in transition mode, do not use their value 0587 } 0588 0589 QList<StateTracker::Index*> ContentPrivate::setTemporaryIndices(const QModelIndex &parent, int start, int end, 0590 const QModelIndex &destination, int row) 0591 { 0592 //FIXME This exists but for now ignoring it hasn't caused too many problems 0593 if (parent != destination) { 0594 qWarning() << "setTemporaryIndices called with corrupted arguments"; 0595 return {}; 0596 } 0597 0598 QList<StateTracker::Index*> ret; 0599 0600 // Before moving them, set a temporary now/col value because it wont be set 0601 // on the index until before endMoveRows is called (but after this 0602 // method returns). 0603 0604 const auto pitem = parent.isValid() ? m_hMapper.value(parent) : m_pRoot; 0605 0606 for (int i = start; i <= end; i++) { 0607 auto idx = m_pModelTracker->modelCandidate()->index(i, 0, parent); 0608 0609 //TODO do not use the hashmap, it is already known 0610 auto elem = pitem->childrenLookup(idx); 0611 Q_ASSERT(elem); 0612 0613 elem->setTemporaryIndex( 0614 destination, row + (i - start), idx.column() 0615 ); 0616 ret << elem; 0617 } 0618 0619 for (int i = row; i <= row + (end - start); i++) { 0620 auto idx = m_pModelTracker->modelCandidate()->index(i, 0, parent); 0621 0622 //TODO do not use the hashmap, it is already known 0623 auto elem = pitem->childrenLookup(idx); 0624 Q_ASSERT(elem); 0625 0626 elem->setTemporaryIndex( 0627 destination, row + (end - start) + 1, idx.column() 0628 ); 0629 ret << elem; 0630 } 0631 0632 return ret; 0633 } 0634 0635 void ContentPrivate::resetTemporaryIndices(const QList<StateTracker::Index*>& indices) 0636 { 0637 for (auto i : qAsConst(indices)) 0638 i->resetTemporaryIndex(); 0639 } 0640 0641 void StateTracker::Content::resetEdges() 0642 { 0643 for (int i = 0; i < (int) EdgeType::NONE; i++) 0644 for (const auto e : {Qt::BottomEdge, Qt::TopEdge, Qt::LeftEdge, Qt::RightEdge}) 0645 setEdge((EdgeType)i, nullptr, e); 0646 } 0647 0648 // Go O(N) for now. Optimize when it becomes a problem (read: soon) 0649 void ContentPrivate::reloadEdges() 0650 { 0651 //FIXME optimize this 0652 0653 enum Range : uint16_t { 0654 TOP = 0x0 << 0, 0655 BUFFER_TOP = 0x1 << 0, 0656 VISIBLE = 0x1 << 1, 0657 BUFFER_BOTTOM = 0x1 << 2, 0658 BOTTOM = 0x1 << 3, 0659 IS_VISIBLE = 0x1 << 4, 0660 IS_NOT_VISIBLE = 0x1 << 5, 0661 IS_BUFFER = 0x1 << 6, 0662 IS_NOT_BUFFER = 0x1 << 7, 0663 }; 0664 0665 Range pos = Range::TOP; 0666 0667 for (auto item = m_pRoot->firstChild(); item; item = item->down()) { 0668 const uint16_t isVisible = item->metadata()->modelTracker()->state() == StateTracker::ModelItem::State::VISIBLE ? 0669 IS_VISIBLE : IS_NOT_VISIBLE; 0670 const uint16_t isBuffer = item->metadata()->modelTracker()->state() == StateTracker::ModelItem::State::BUFFER ? 0671 IS_BUFFER : IS_NOT_BUFFER; 0672 0673 switch(pos | isVisible | isBuffer) { 0674 case Range::TOP | Range::IS_BUFFER: 0675 pos = Range::BUFFER_TOP; 0676 q_ptr->setEdge(EdgeType::BUFFERED, item, Qt::TopEdge); 0677 break; 0678 case Range::TOP | Range::IS_VISIBLE: 0679 case Range::BUFFER_TOP | Range::IS_VISIBLE: 0680 pos = Range::VISIBLE; 0681 q_ptr->setEdge(EdgeType::VISIBLE, item, Qt::TopEdge); 0682 break; 0683 case Range::VISIBLE | Range::IS_NOT_VISIBLE: 0684 case Range::VISIBLE | Range::IS_NOT_BUFFER: 0685 q_ptr->setEdge(EdgeType::VISIBLE, item->up(), Qt::BottomEdge); 0686 pos = Range::BOTTOM; 0687 break; 0688 case Range::VISIBLE | Range::IS_NOT_VISIBLE | Range::IS_BUFFER: 0689 pos = Range::BUFFER_BOTTOM; 0690 break; 0691 case Range::VISIBLE | Range::IS_NOT_BUFFER | Range::IS_NOT_VISIBLE: 0692 q_ptr->setEdge(EdgeType::BUFFERED, item->up(), Qt::BottomEdge); 0693 pos = Range::BOTTOM; 0694 break; 0695 } 0696 } 0697 } 0698 0699 void ContentPrivate::enterState(IndexMetadata *tti, StateTracker::ModelItem::State s) 0700 { 0701 Q_UNUSED(tti); 0702 Q_UNUSED(s); 0703 //TODO count the number of active objects in each states for the "frame" load balancing 0704 } 0705 0706 void ContentPrivate::leaveState(IndexMetadata *tti, StateTracker::ModelItem::State s) 0707 { 0708 Q_UNUSED(tti); 0709 Q_UNUSED(s); 0710 //TODO count the number of active objects in each states for the "frame" load balancing 0711 } 0712 0713 void ContentPrivate::error(IndexMetadata *, StateTracker::ModelItem::State) 0714 { 0715 Q_ASSERT(false); 0716 } 0717 0718 StateTracker::Index *StateTracker::Content::firstItem() const 0719 { 0720 return root()->firstChild(); 0721 } 0722 0723 StateTracker::Index *StateTracker::Content::lastItem() const 0724 { 0725 auto candidate = d_ptr->m_pRoot->lastChild(); 0726 0727 while (candidate && candidate->lastChild()) 0728 candidate = candidate->lastChild(); 0729 0730 return candidate; 0731 } 0732 0733 bool ContentPrivate::isInsertActive(const QModelIndex& p, int first, int last) const 0734 { 0735 Q_UNUSED(last) //TODO 0736 auto pitem = p.isValid() ? m_hMapper.value(p) : m_pRoot; 0737 0738 StateTracker::Index *prev(nullptr); 0739 0740 //FIXME use up() 0741 if (first && pitem) 0742 prev = pitem->childrenLookup(m_pModelTracker->modelCandidate()->index(first-1, 0, p)); 0743 0744 if (first && !prev) 0745 return false; 0746 0747 if (q_ptr->edges(EdgeType::FREE)->m_Edges & (Qt::TopEdge|Qt::BottomEdge)) 0748 return true; 0749 0750 const auto parent = p.isValid() ? ttiForIndex(p) : m_pRoot; 0751 0752 // It's controversial as it means if the items would otherwise be 0753 // visible because their parent "virtual" position + insertion height 0754 // could happen to be in the view but in this case the scrollbar would 0755 // move. Mobile views tend to not shuffle the current content around so 0756 // from that point of view it is correct, but if the item is in the 0757 // buffer, then it will move so doing this is a bit buggy. 0758 return parent != nullptr; 0759 } 0760 0761 /// Return true if the indices affect the current view 0762 bool StateTracker::Content::isActive(const QModelIndex& parent, int first, int last) 0763 { 0764 if (!parent.isValid()) { 0765 // There is nothing loaded, so everything is active (so far) 0766 if (!d_ptr->m_pRoot->firstChild()) 0767 return true; 0768 0769 // There is room for more. Assuming the code that makes sure all items 0770 // that can be loaded are, then it only happens when there is room for 0771 // more. 0772 if (edges(EdgeType::FREE)->m_Edges & (Qt::BottomEdge | Qt::TopEdge)) 0773 return true; 0774 0775 const QRect loadedRect( 0776 d_ptr->m_pRoot->firstChild()->index().column(), 0777 d_ptr->m_pRoot->firstChild()->index().row(), 0778 d_ptr->m_pRoot->lastChild ()->index().column() + 1, 0779 d_ptr->m_pRoot->lastChild ()->index().row() + 1 0780 ); 0781 0782 const QRect changedRect( 0783 0, 0784 first, 0785 1, //FIXME use columnCount+!? 0786 last 0787 ); 0788 0789 return loadedRect.intersects(changedRect); 0790 } 0791 0792 //FIXME This method is incomplete 0793 // Q_ASSERT(false); //TODO THIS_COMMIT 0794 0795 return true; 0796 } 0797 0798 /// Add new entries to the mapping 0799 StateTracker::ModelItem* ContentPrivate::addChildren(const QModelIndex& index) 0800 { 0801 Q_ASSERT(index.isValid() && !m_hMapper.contains(index)); 0802 0803 auto e = new StateTracker::ModelItem(m_pViewport); 0804 e->setModelIndex(index); 0805 0806 m_hMapper[index] = e; 0807 0808 return e; 0809 } 0810 0811 void ContentPrivate::slotCleanup() 0812 { 0813 // The whole slotCleanup cycle isn't necessary, it wont find anything. 0814 if (!m_pRoot->firstChild()) 0815 return; 0816 0817 m_pModelTracker << StateTracker::Model::Action::RESET; 0818 0819 m_pRoot->metadata() 0820 << IndexMetadata::LoadAction::HIDE 0821 << IndexMetadata::LoadAction::DETACH; 0822 0823 m_hMapper.clear(); 0824 m_pRoot = new StateTracker::ModelItem(m_pViewport); 0825 0826 // Reset the edges 0827 for (int i = 0; i < 3; i++) 0828 m_lRects[i] = ModelRect(); 0829 } 0830 0831 StateTracker::ModelItem* ContentPrivate::ttiForIndex(const QModelIndex& idx) const 0832 { 0833 if (!idx.isValid()) 0834 return nullptr; 0835 0836 const auto parent = (!idx.parent().isValid()) ? 0837 m_pRoot : m_hMapper.value(idx.parent()); 0838 0839 return parent ? 0840 static_cast<StateTracker::ModelItem*>(parent->childrenLookup(idx)) : nullptr; 0841 } 0842 0843 IndexMetadata *StateTracker::Content::metadataForIndex(const QModelIndex& idx) const 0844 { 0845 const auto tti = d_ptr->ttiForIndex(idx); 0846 0847 return tti ? tti->metadata() : nullptr; 0848 } 0849 0850 void StateTracker::Content::setAvailableEdges(Qt::Edges es, EdgeType t) 0851 { 0852 edges(t)->m_Edges = es; 0853 } 0854 0855 Qt::Edges StateTracker::Content::availableEdges(EdgeType t) const 0856 { 0857 return edges(t)->m_Edges; 0858 } 0859 0860 IndexMetadata *StateTracker::Content::getEdge(EdgeType t, Qt::Edge e) const 0861 { 0862 const auto ret = edges(t)->getEdge(e); 0863 0864 return ret ? ret->metadata() : nullptr; 0865 } 0866 0867 ModelRect* StateTracker::Content::edges(EdgeType e) const 0868 { 0869 Q_ASSERT((int) e >= 0 && (int)e <= 2); 0870 return (ModelRect*) &d_ptr->m_lRects[(int)e]; 0871 } 0872 0873 // Add more validation to detect possible invalid edges being set 0874 void StateTracker::Content:: 0875 setEdge(EdgeType et, StateTracker::Index* tti, Qt::Edge e) 0876 { 0877 //DEBUG this only works with proxy models, not JIT/AOT strategies 0878 // if (tti && et == EdgeType::VISIBLE) 0879 // Q_ASSERT(TTI(tti)->metadata()->isValid()); 0880 0881 edges(et)->setEdge(tti, e); 0882 0883 // Only test when `tti` is true because when removing the last item, it 0884 // will always need to remove both the top and bottom, so removing the 0885 // top will fail the test. 0886 if (tti) { _DO_TEST(_test_validate_edges_simple, this) } 0887 } 0888 0889 StateTracker::Index *StateTracker::Content:: 0890 find(StateTracker::Index *from, Qt::Edge direction, std::function<bool(StateTracker::Index *i)> cond) const 0891 { 0892 auto f = from; 0893 while(f && cond(f) && f->next(direction) && (f = f->next(direction))); 0894 return f; 0895 } 0896 0897 StateTracker::Model *StateTracker::Content::modelTracker() const 0898 { 0899 return d_ptr->m_pModelTracker; 0900 } 0901 0902 //DEPRECATED use StateTracker::Proximity 0903 QModelIndex ContentPrivate::getNextIndex(const QModelIndex& idx) const 0904 { 0905 // There is 2 possibilities, a sibling or a [[great]grand]uncle 0906 0907 if (m_pModelTracker->modelCandidate()->rowCount(idx)) 0908 return m_pModelTracker->modelCandidate()->index(0,0, idx); 0909 0910 auto sib = idx.sibling(idx.row()+1, idx.column()); 0911 0912 if (sib.isValid()) 0913 return sib; 0914 0915 if (!idx.parent().isValid()) 0916 return {}; 0917 0918 auto p = idx.parent(); 0919 0920 while (p.isValid()) { 0921 sib = p.sibling(p.row()+1, p.column()); 0922 if (sib.isValid()) 0923 return sib; 0924 0925 p = p.parent(); 0926 } 0927 0928 return {}; 0929 } 0930 0931 void StateTracker::Content::connectModel(QAbstractItemModel *m) 0932 { 0933 QObject::connect(m, &QAbstractItemModel::rowsInserted, d_ptr, 0934 &ContentPrivate::slotRowsInserted ); 0935 QObject::connect(m, &QAbstractItemModel::rowsAboutToBeRemoved, d_ptr, 0936 &ContentPrivate::slotRowsRemoved ); 0937 QObject::connect(m, &QAbstractItemModel::layoutAboutToBeChanged, d_ptr, 0938 &ContentPrivate::slotCleanup); 0939 QObject::connect(m, &QAbstractItemModel::layoutChanged, d_ptr, 0940 &ContentPrivate::slotLayoutChanged); 0941 QObject::connect(m, &QAbstractItemModel::modelAboutToBeReset, d_ptr, 0942 &ContentPrivate::slotCleanup); 0943 QObject::connect(m, &QAbstractItemModel::modelReset, d_ptr, 0944 &ContentPrivate::slotLayoutChanged); 0945 QObject::connect(m, &QAbstractItemModel::rowsAboutToBeMoved, d_ptr, 0946 &ContentPrivate::slotRowsMoved); 0947 QObject::connect(m, &QAbstractItemModel::dataChanged, d_ptr, 0948 &ContentPrivate::slotDataChanged); 0949 0950 #ifdef ENABLE_EXTRA_VALIDATION 0951 QObject::connect(m, &QAbstractItemModel::rowsMoved, d_ptr, 0952 [this](){_DO_TEST(_test_validateLinkedList, this);}); 0953 QObject::connect(m, &QAbstractItemModel::rowsRemoved, d_ptr, 0954 [this](){_DO_TEST(_test_validateLinkedList, this);}); 0955 #endif 0956 } 0957 0958 void StateTracker::Content::disconnectModel(QAbstractItemModel *m) 0959 { 0960 QObject::disconnect(m, &QAbstractItemModel::rowsInserted, d_ptr, 0961 &ContentPrivate::slotRowsInserted); 0962 QObject::disconnect(m, &QAbstractItemModel::rowsAboutToBeRemoved, d_ptr, 0963 &ContentPrivate::slotRowsRemoved); 0964 QObject::disconnect(m, &QAbstractItemModel::layoutAboutToBeChanged, d_ptr, 0965 &ContentPrivate::slotCleanup); 0966 QObject::disconnect(m, &QAbstractItemModel::layoutChanged, d_ptr, 0967 &ContentPrivate::slotLayoutChanged); 0968 QObject::disconnect(m, &QAbstractItemModel::modelAboutToBeReset, d_ptr, 0969 &ContentPrivate::slotCleanup); 0970 QObject::disconnect(m, &QAbstractItemModel::modelReset, d_ptr, 0971 &ContentPrivate::slotLayoutChanged); 0972 QObject::disconnect(m, &QAbstractItemModel::rowsAboutToBeMoved, d_ptr, 0973 &ContentPrivate::slotRowsMoved); 0974 QObject::disconnect(m, &QAbstractItemModel::dataChanged, d_ptr, 0975 &ContentPrivate::slotDataChanged); 0976 0977 #ifdef ENABLE_EXTRA_VALIDATION 0978 // QObject::disconnect(m, &QAbstractItemModel::rowsMoved, d_ptr, 0979 // [this](){d_ptr->_test_validateLinkedList();}); 0980 // QObject::disconnect(m, &QAbstractItemModel::rowsRemoved, d_ptr, 0981 // [this](){d_ptr->_test_validateLinkedList();}); 0982 #endif 0983 } 0984 0985 StateTracker::Index *StateTracker::Content::root() const 0986 { 0987 return d_ptr->m_pRoot; 0988 } 0989 0990 void StateTracker::Content::resetRoot() 0991 { 0992 root()->metadata() << IndexMetadata::LoadAction::RESET; 0993 0994 for (int i = 0; i < 3; i++) 0995 d_ptr->m_lRects[i] = {}; 0996 0997 d_ptr->m_hMapper.clear(); 0998 delete d_ptr->m_pRoot; 0999 d_ptr->m_pRoot = new StateTracker::ModelItem(d_ptr->m_pViewport); 1000 } 1001 1002 void StateTracker::Content::perfromStateChange(Event e, IndexMetadata *md, StateTracker::ModelItem::State s) 1003 { 1004 const auto ms = e == Event::LEAVE_STATE ? s : md->modelTracker()->state(); 1005 (d_ptr->*ContentPrivate::m_fStateLogging[(int)ms][(int)e])(md, s); 1006 } 1007 1008 void StateTracker::Content::forceInsert(const QModelIndex& idx) 1009 { 1010 //TODO add some safety check or find a way to get rid of this hack 1011 d_ptr->slotRowsInserted(idx.parent(), idx.row(), idx.row()); 1012 } 1013 1014 // Also make sure not to load the same element twice 1015 void StateTracker::Content::forceInsert(const QModelIndex& parent, int first, int last) 1016 { 1017 auto parNode = parent.isValid() ? d_ptr->m_hMapper[parent] : d_ptr->m_pRoot; 1018 1019 // If the parent isn't loaded, there is no risk of collision 1020 if (!parNode) { 1021 //TODO loading the parent chain of `parent` is needed 1022 d_ptr->slotRowsInserted(parent, first, last); 1023 return; 1024 } 1025 1026 // If the parent has no children elements, then there is no collisions 1027 if (!parNode->firstChild()) { 1028 d_ptr->slotRowsInserted(parent, first, last); 1029 return; 1030 } 1031 1032 // If the range isn't overlapping, there is no collisions 1033 if (parNode->lastChild()->effectiveRow() < first || parNode->firstChild()->effectiveRow() > last) { 1034 d_ptr->slotRowsInserted(parent, first, last); 1035 return; 1036 } 1037 1038 // If there is a continuity encompassing `first` and `last` then there 1039 // is nothing to do. 1040 if (parNode->firstChild()->continuityTracker()->size() >= last) { 1041 return; 1042 } 1043 1044 //TODO the case above only works if firstChild()->row() == 0. If another 1045 // case is hit, then it will assert in slotRowsInserted 1046 1047 d_ptr->slotRowsInserted(parent, first, last); 1048 } 1049 1050 #include <statetracker/content_p.moc>