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>