File indexing completed on 2024-05-12 04:43:04

0001 /***************************************************************************
0002  *   Copyright (C) 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 "model_p.h"
0019 
0020 #include <QtCore/QDebug>
0021 
0022 #include <private/indexmetadata_p.h>
0023 #include <private/statetracker/content_p.h>
0024 #include <private/statetracker/index_p.h>
0025 
0026 using EdgeType = IndexMetadata::EdgeType;
0027 
0028 #include <QtGlobal>
0029 
0030 #define S StateTracker::Model::State::
0031 const StateTracker::Model::State StateTracker::Model::m_fStateMap[6][7] = {
0032 /*                POPULATE     DISABLE       ENABLE       RESET         FREE         MOVE         TRIM   */
0033 /*NO_MODEL */ { S NO_MODEL , S NO_MODEL , S NO_MODEL , S NO_MODEL , S NO_MODEL , S NO_MODEL , S NO_MODEL },
0034 /*PAUSED   */ { S POPULATED, S PAUSED   , S TRACKING , S PAUSED   , S PAUSED   , S PAUSED   , S PAUSED   },
0035 /*POPULATED*/ { S TRACKING , S PAUSED   , S TRACKING , S RESETING , S POPULATED, S POPULATED, S POPULATED},
0036 /*TRACKING */ { S TRACKING , S POPULATED, S TRACKING , S RESETING , S TRACKING , S TRACKING , S TRACKING },
0037 /*RESETING */ { S RESETING , S RESETING , S TRACKING , S RESETING , S PAUSED   , S RESETING , S RESETING },
0038 /*MUTATING */ { S MUTATING , S MUTATING , S MUTATING , S MUTATING , S MUTATING , S MUTATING , S MUTATING },
0039 };
0040 #undef S
0041 
0042 // This state machine is self healing, error can be called in release mode
0043 // and it will only disable the view without further drama.
0044 #define A &StateTracker::Model::
0045 const StateTracker::Model::StateF StateTracker::Model::m_fStateMachine[6][7] = {
0046 /*               POPULATE     DISABLE    ENABLE     RESET       FREE       MOVE   ,   TRIM */
0047 /*NO_MODEL */ { A nothing , A nothing, A nothing, A nothing, A nothing, A nothing , A error },
0048 /*PAUSED   */ { A populate, A nothing, A error  , A nothing, A free   , A nothing , A trim  },
0049 /*POPULATED*/ { A error   , A nothing, A track  , A reset  , A free   , A fill    , A trim  },
0050 /*TRACKING */ { A nothing , A untrack, A nothing, A reset  , A free   , A fill    , A trim  },
0051 /*RESETING */ { A nothing , A error  , A track  , A error  , A free   , A error   , A error },
0052 /*MUTATING */ { A nothing , A nothing, A nothing, A nothing, A nothing, A nothing , A nothing},
0053 };
0054 
0055 StateTracker::Model::Model(StateTracker::Content* d) : q_ptr(d)
0056 {}
0057 
0058 StateTracker::Model::State StateTracker::Model::performAction(Action a)
0059 {
0060     const int s = (int)m_State;
0061     m_State = m_fStateMap[s][(int)a];
0062 
0063     (this->*StateTracker::Model::m_fStateMachine[s][(int)a])();
0064 
0065     return m_State;
0066 }
0067 
0068 StateTracker::Model::State StateTracker::Model::state() const
0069 {
0070     return m_State;
0071 }
0072 
0073 void StateTracker::Model::track()
0074 {
0075     Q_ASSERT(m_pModel && !m_pTrackedModel);
0076 
0077     q_ptr->connectModel(m_pModel);
0078 
0079     m_pTrackedModel = m_pModel;
0080 }
0081 
0082 void StateTracker::Model::untrack()
0083 {
0084     Q_ASSERT(m_pTrackedModel);
0085     if (!m_pTrackedModel) //TODO fix the state machine not to get here
0086         return;
0087 
0088     q_ptr->disconnectModel(m_pTrackedModel);
0089 
0090     m_pTrackedModel = nullptr;
0091 }
0092 
0093 void StateTracker::Model::free()
0094 {
0095     q_ptr->resetRoot();
0096 }
0097 
0098 void StateTracker::Model::reset()
0099 {
0100     const bool wasTracked = m_pTrackedModel != nullptr;
0101 
0102     if (wasTracked)
0103         untrack(); //TODO THIS_COMMIT
0104 
0105     q_ptr->root()->metadata() << IndexMetadata::LoadAction::RESET;
0106     q_ptr->resetEdges();
0107 
0108     //HACK
0109     if (!m_pModel) {
0110         m_State = State::NO_MODEL;
0111         return;
0112     }
0113 
0114     // Make sure it never stay in RESETING mode outside of the method
0115     performAction(wasTracked ? Action::ENABLE : Action::FREE);
0116 }
0117 
0118 /**
0119  * Keep loading more and more items until the view is filled.
0120  *
0121  * @todo Eventually add an adapter to track what should and should not be loaded
0122  * instead of using edges.
0123  */
0124 void StateTracker::Model::populate()
0125 {
0126     Q_ASSERT(m_pModel);
0127 
0128     _DO_TEST(_test_validateContinuity, q_ptr);
0129 
0130     if (!q_ptr->edges(EdgeType::FREE)->m_Edges)
0131         return;
0132 
0133     //HACK stash the state, this needs more refactoring
0134     // Sometime, if the viewport size depends on the content size, inserting
0135     // will cause some viewport "Resize" events which will call `populate`.
0136     // This could potentially create a very confusing situation where in single
0137     // QModelIndex is being inserted recursively and a stack overflow.
0138     const auto s = m_State;
0139     m_State = State::MUTATING;
0140 
0141     if (q_ptr->root()->firstChild() && (q_ptr->edges(EdgeType::FREE)->m_Edges & (Qt::TopEdge|Qt::BottomEdge))) {
0142         while (q_ptr->edges(EdgeType::FREE)->m_Edges & Qt::TopEdge) {
0143             const auto was = q_ptr->edges(EdgeType::VISIBLE)->getEdge(Qt::TopEdge);
0144             //FIXME when everything fails to load, it would otherwise make an infinite loop
0145             if (!was)
0146                 break;
0147 
0148             const auto u = was->metadata()->modelTracker()->load(Qt::TopEdge);
0149 
0150             Q_ASSERT(u || q_ptr->edges(EdgeType::VISIBLE)->getEdge(Qt::TopEdge)->effectiveRow() == 0);
0151             Q_ASSERT(u || !q_ptr->edges(EdgeType::VISIBLE)->getEdge(Qt::TopEdge)->effectiveParentIndex().isValid());
0152 
0153             if (!u)
0154                 break;
0155 
0156             u->metadata() << IndexMetadata::LoadAction::SHOW;
0157         }
0158 
0159         while (q_ptr->edges(EdgeType::FREE)->m_Edges & Qt::BottomEdge) {
0160             const auto was = q_ptr->edges(EdgeType::VISIBLE)->getEdge(Qt::BottomEdge);
0161 
0162             //FIXME when everything fails to load, it would otherwise make an infinite loop
0163             if (!was)
0164                 break;
0165 
0166             const auto u = was->metadata()->modelTracker()->load(Qt::BottomEdge);
0167 
0168             if (!u)
0169                 break;
0170 
0171             u->metadata() << IndexMetadata::LoadAction::SHOW;
0172         }
0173     }
0174     else if (auto rc = m_pModel->rowCount()) {
0175 
0176         //TODO support anchors (load from the bottom)
0177         q_ptr->forceInsert({}, 0, rc - 1);
0178 
0179     }
0180 
0181     //HACK Restore the stashed state
0182     m_State = s;
0183 
0184     if (q_ptr->edges(EdgeType::FREE)->m_Edges & Qt::BottomEdge) {
0185         _DO_TEST(_test_validateAtEnd, q_ptr);
0186     }
0187 }
0188 
0189 void StateTracker::Model::trim()
0190 {
0191     //FIXME For now the delegates are only freed when the QModelIndex is removed.
0192     // This also prevent pooling from being used/finished.
0193     return;
0194 
0195 //     QTimer::singleShot(0, [this]() {
0196 //         if (m_TrackingState != StateTracker::Content::TrackingState::TRACKING)
0197 //             return;
0198 //
0199 //         _test_validateViewport();
0200 //
0201 //         qDebug() << "TRIM" << edges(EdgeType::VISIBLE)->m_Edges;
0202 //     //     if (!edges(EdgeType::FREE)->getEdge(Qt::TopEdge)) {
0203 //     //         Q_ASSERT(!edges(EdgeType::FREE)->getEdge(Qt::BottomEdge));
0204 //     //         return;
0205 //     //     }
0206 //
0207 //         auto elem = TTI(edges(EdgeType::VISIBLE)->getEdge(Qt::TopEdge)); //FIXME have a visual and reacheable rect
0208 //
0209 //         while (!(edges(EdgeType::FREE)->m_Edges & Qt::TopEdge)) {
0210 //             Q_ASSERT(elem);
0211 //             Q_ASSERT(edges(EdgeType::FREE)->getEdge(Qt::TopEdge));
0212 //             Q_ASSERT(!m_pViewport->currentRect().intersects(elem->metadata()->geometry()));
0213 //
0214 //             elem->metadata() << IndexMetadata::LoadAction::HIDE);
0215 //             elem = TTI(edges(EdgeType::VISIBLE)->getEdge(Qt::TopEdge));
0216 //         }
0217 //
0218 //         elem = TTI(edges(EdgeType::VISIBLE)->getEdge(Qt::BottomEdge)); //FIXME have a visual and reacheable rect
0219 //         while (!(edges(EdgeType::FREE)->m_Edges & Qt::BottomEdge)) {
0220 //             Q_ASSERT(elem);
0221 //             Q_ASSERT(!m_pViewport->currentRect().intersects(elem->metadata()->geometry()));
0222 //             elem->metadata() << IndexMetadata::LoadAction::HIDE);
0223 //             elem = TTI(edges(EdgeType::VISIBLE)->getEdge(Qt::BottomEdge));
0224 //         }
0225 //     });
0226 
0227 
0228     // Unload everything below the cache area to get rid of the insertion/moving
0229     // overhead. Not doing so would also require to handle "holes" in the tree
0230     // which is very complex and unnecessary.
0231     StateTracker::Index *be = q_ptr->edges(EdgeType::VISIBLE)->getEdge(Qt::BottomEdge); //TODO use BUFFERED
0232 
0233     if (!be)
0234         return;
0235 
0236     StateTracker::Index *item = q_ptr->lastItem();
0237 
0238     if (be == item)
0239         return;
0240 
0241     do {
0242         item->metadata() << IndexMetadata::LoadAction::DETACH;
0243     } while((item = item->up()) != be);
0244 }
0245 
0246 void StateTracker::Model::fill()
0247 {
0248     trim();
0249     populate();
0250 }
0251 
0252 void StateTracker::Model::nothing()
0253 {}
0254 
0255 void StateTracker::Model::error()
0256 {
0257     Q_ASSERT(false);
0258     this << StateTracker::Model::Action::DISABLE
0259          << StateTracker::Model::Action::RESET
0260          << StateTracker::Model::Action::ENABLE;
0261 }
0262 
0263 QAbstractItemModel *StateTracker::Model::trackedModel() const
0264 {
0265     Q_ASSERT(m_pTrackedModel);
0266     return m_pTrackedModel;
0267 }
0268 
0269 QAbstractItemModel *StateTracker::Model::modelCandidate() const
0270 {
0271     return m_pModel;
0272 }
0273 
0274 void StateTracker::Model::setModel(QAbstractItemModel* m)
0275 {
0276     if (m == m_pTrackedModel)
0277         return;
0278 
0279     this << StateTracker::Model::Action::DISABLE
0280          << StateTracker::Model::Action::RESET
0281          << StateTracker::Model::Action::FREE;
0282 
0283     //_test_validateModelAboutToReplace();
0284 
0285     m_pModel = m;
0286 
0287     if (m) {
0288         Q_ASSERT(m_State == State::NO_MODEL || m_State == State::PAUSED);
0289         m_State = State::PAUSED;
0290     }
0291 
0292     if (!m)
0293         return;
0294 }
0295