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