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

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 "modelitem_p.h"
0019 
0020 #include "proximity_p.h"
0021 #include <private/statetracker/content_p.h>
0022 #include <viewport.h>
0023 #include <private/viewport_p.h>
0024 #include <private/statetracker/viewitem_p.h>
0025 
0026 #define S StateTracker::ModelItem::State::
0027 const StateTracker::ModelItem::State StateTracker::ModelItem::m_fStateMap[8][8] = {
0028 /*                 SHOW         HIDE        ATTACH     DETACH      UPDATE       MOVE         RESET     REPARENT */
0029 /*NEW      */ { S ERROR  , S ERROR    , S REACHABLE, S ERROR    , S ERROR  , S ERROR    , S ERROR    , S ERROR  },
0030 /*BUFFER   */ { S VISIBLE, S BUFFER   , S BUFFER   , S DANGLING , S BUFFER , S BUFFER   , S BUFFER   , S MOVING },
0031 /*REMOVED  */ { S ERROR  , S ERROR    , S ERROR    , S REACHABLE, S ERROR  , S ERROR    , S ERROR    , S MOVING },
0032 /*REACHABLE*/ { S VISIBLE, S REACHABLE, S ERROR    , S REACHABLE, S ERROR  , S REACHABLE, S REACHABLE, S MOVING },
0033 /*VISIBLE  */ { S VISIBLE, S BUFFER   , S ERROR    , S ERROR    , S VISIBLE, S VISIBLE  , S VISIBLE  , S MOVING },
0034 /*ERROR    */ { S ERROR  , S ERROR    , S ERROR    , S ERROR    , S ERROR  , S ERROR    , S ERROR    , S ERROR  },
0035 /*DANGLING */ { S ERROR  , S ERROR    , S ERROR    , S ERROR    , S ERROR  , S ERROR    , S ERROR    , S ERROR  },
0036 /*MOVING   */ { S VISIBLE, S ERROR    , S ERROR    , S ERROR    , S ERROR  , S VISIBLE  , S ERROR    , S BUFFER },
0037 };
0038 #undef S
0039 
0040 #define A &StateTracker::ModelItem::
0041 const StateTracker::ModelItem::StateF StateTracker::ModelItem::m_fStateMachine[8][8] = {
0042 /*                 SHOW       HIDE      ATTACH     DETACH      UPDATE     MOVE      RESET    REPARENT */
0043 /*NEW      */ { A error  , A nothing, A nothing, A error   , A error  , A error  , A error, A error   },
0044 /*BUFFER   */ { A show   , A remove2, A attach , A destroy , A refresh, A move   , A reset, A nothing },
0045 /*REMOVED  */ { A error  , A error  , A error  , A detach  , A error  , A error  , A reset, A nothing },
0046 /*REACHABLE*/ { A show   , A nothing, A error  , A detach  , A error  , A move   , A reset, A nothing },
0047 /*VISIBLE  */ { A nothing, A hide   , A error  , A error   , A refresh, A move   , A reset, A nothing },
0048 /*ERROR    */ { A error  , A error  , A error  , A error   , A error  , A error  , A error, A error   },
0049 /*DANGLING */ { A error  , A error  , A error  , A error   , A error  , A error  , A error, A error   },
0050 /*MOVING   */ { A move   , A error  , A error  , A error   , A error  , A nothing, A error, A nothing },
0051 };
0052 #undef A
0053 
0054 
0055 StateTracker::ModelItem::ModelItem(Viewport *v):
0056   StateTracker::Index(v), q_ptr(v->s_ptr->m_pReflector)
0057 {}
0058 
0059 StateTracker::ModelItem* StateTracker::ModelItem::load(Qt::Edge e) const
0060 {
0061     // First, if it's already loaded, then don't bother
0062     if (auto ret = next(e))
0063         return ret->metadata()->modelTracker();
0064 
0065     const auto t = metadata()->proximityTracker();
0066     const QModelIndexList l = t->getNext(e);
0067 
0068     // The consumer of this function only cares about the item above `this`,
0069     // not all the items that were loaded because they are dependencies.
0070     StateTracker::Index *i = nullptr;
0071 
0072     for (const QModelIndex& idx : qAsConst(l)) {
0073         Q_ASSERT(idx.isValid());
0074         q_ptr->forceInsert(idx);
0075     }
0076 
0077     // This works because only the TopEdge and LeftEdge can return multiple `l`
0078     i = next(e);
0079 
0080     _DO_TEST(_test_validateViewport, q_ptr);
0081 
0082     return i ? i->metadata()->modelTracker() : nullptr;
0083 }
0084 
0085 /**
0086  * When the item is moved, the old loading state is worthless.
0087  *
0088  * Recompute it based on the siblings.
0089  */
0090 void StateTracker::ModelItem::rebuildState()
0091 {
0092     static const auto VISIBLE = StateTracker::ModelItem::State::VISIBLE;
0093 
0094     //TODO make a 3D matrix out of this
0095     auto u = up  () ? up  ()->metadata()->modelTracker() : nullptr;
0096     auto d = down() ? down()->metadata()->modelTracker() : nullptr;
0097 
0098     const bool betweenVis = u && d && u->state() == VISIBLE && d->state() == VISIBLE;
0099     const bool nearVisible = ((!u) && d && d->state() == VISIBLE) ||
0100         ((!d) && u && u->state() == VISIBLE);
0101 
0102     if (betweenVis || nearVisible)
0103         m_State = StateTracker::ModelItem::State::VISIBLE;
0104     else
0105         Q_ASSERT(false); //TODO not implemented
0106 }
0107 
0108 IndexMetadata::EdgeType StateTracker::ModelItem::isTopEdge() const
0109 {
0110     //FIXME use the ModelItem cache
0111 
0112     const auto r = metadata()->viewport()->s_ptr->m_pReflector;
0113 
0114     if (r->getEdge(IndexMetadata::EdgeType::VISIBLE, Qt::TopEdge)) {
0115         return IndexMetadata::EdgeType::VISIBLE;
0116     }
0117 
0118     if (r->getEdge(IndexMetadata::EdgeType::VISIBLE, Qt::TopEdge)) {
0119         return IndexMetadata::EdgeType::BUFFERED;
0120     }
0121 
0122     return IndexMetadata::EdgeType::NONE;
0123 }
0124 
0125 IndexMetadata::EdgeType StateTracker::ModelItem::isBottomEdge() const
0126 {
0127     //FIXME use the ModelItem cache
0128 
0129     const auto r = metadata()->viewport()->s_ptr->m_pReflector;
0130 
0131     if (r->getEdge(IndexMetadata::EdgeType::VISIBLE, Qt::BottomEdge)) {
0132         return IndexMetadata::EdgeType::VISIBLE;
0133     }
0134 
0135     if (r->getEdge(IndexMetadata::EdgeType::VISIBLE, Qt::BottomEdge)) {
0136         return IndexMetadata::EdgeType::BUFFERED;
0137     }
0138 
0139     return IndexMetadata::EdgeType::NONE;
0140 }
0141 
0142 StateTracker::ModelItem::State StateTracker::ModelItem::state() const
0143 {
0144     return m_State;
0145 }
0146 
0147 void StateTracker::ModelItem::remove(bool reparent)
0148 {
0149     // First, update the edges
0150     const auto te = isTopEdge();
0151     const auto be = isBottomEdge();
0152 
0153     //TODO turn this into a state tracker, really
0154     switch(te) {
0155         case IndexMetadata::EdgeType::NONE:
0156             break;
0157         case IndexMetadata::EdgeType::FREE:
0158             Q_ASSERT(false); // Impossible, only corrupted memory can cause this
0159             break;
0160         case IndexMetadata::EdgeType::VISIBLE:
0161             if (auto i = q_ptr->find(this, Qt::TopEdge, [](auto i) -> bool {
0162               return i->metadata()->modelTracker()->m_State == StateTracker::ModelItem::State::VISIBLE; } )) {
0163                 Q_ASSERT(i->metadata()->modelTracker()->m_State == StateTracker::ModelItem::State::VISIBLE); //TODO
0164                 q_ptr->setEdge(IndexMetadata::EdgeType::VISIBLE, i, Qt::TopEdge);
0165             }
0166             else {
0167                 Q_ASSERT(false); //TODO clear the edges
0168             }
0169             break;
0170         case IndexMetadata::EdgeType::BUFFERED:
0171             Q_ASSERT(false); //TODO as of the time of this comment, it's hald implemented
0172             break;
0173     }
0174 
0175     switch(be) {
0176         case IndexMetadata::EdgeType::NONE:
0177             break;
0178         case IndexMetadata::EdgeType::FREE:
0179             Q_ASSERT(false); // Impossible, only corrupted memory can cause this
0180             break;
0181         case IndexMetadata::EdgeType::VISIBLE:
0182             if (auto i = q_ptr->find(this, Qt::BottomEdge, [](auto i) -> bool {
0183               return i->metadata()->modelTracker()->m_State == StateTracker::ModelItem::State::VISIBLE; } )) {
0184                 Q_ASSERT(i->metadata()->modelTracker()->m_State == StateTracker::ModelItem::State::VISIBLE); //TODO
0185                 q_ptr->setEdge(IndexMetadata::EdgeType::VISIBLE, i, Qt::BottomEdge);
0186             }
0187             else {
0188                 Q_ASSERT(false); //TODO clear the edges
0189             }
0190             break;
0191         case IndexMetadata::EdgeType::BUFFERED:
0192             Q_ASSERT(false); //TODO as of the time of this comment, it's hald implemented
0193             break;
0194     }
0195 
0196     metadata()->viewport()->s_ptr->notifyRemoval(metadata());
0197     metadata() << IndexMetadata::LoadAction::REPARENT;
0198     Index::remove(reparent);
0199 }
0200 
0201 bool StateTracker::ModelItem::nothing()
0202 { return true; }
0203 
0204 #pragma GCC diagnostic push
0205 #pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn"
0206 bool StateTracker::ModelItem::error()
0207 {
0208     Q_ASSERT(false);
0209     return false;
0210 }
0211 #pragma GCC diagnostic pop
0212 
0213 bool StateTracker::ModelItem::show()
0214 {
0215     Q_ASSERT(m_State == State::VISIBLE);
0216 
0217     if (metadata()->viewTracker()) {
0218         //TODO Implementing this *may* make sense, but for now avoid it.
0219         // for now it is undefined behavior, but not fatal
0220         Q_ASSERT(false); //TODO not well thought, most definitely broken
0221         //item->setVisible(true);
0222     }
0223     else {
0224         Q_ASSERT(metadata()->viewport()->s_ptr->m_fFactory);
0225         metadata()->setViewTracker(metadata()->viewport()->s_ptr->m_fFactory()->s_ptr);
0226         Q_ASSERT(metadata()->viewTracker());
0227 
0228         metadata() << IndexMetadata::ViewAction::ATTACH;
0229         Q_ASSERT(metadata()->viewTracker()->state() == StateTracker::ViewItem::State::POOLED);
0230         Q_ASSERT(m_State == State::VISIBLE);
0231 
0232         //DEBUG
0233         //if (auto item = metadata()->viewTracker()->item())
0234         //    qDebug() << "CREATE" << this << item->y() << item->height();
0235     }
0236 
0237     metadata() << IndexMetadata::ViewAction::ENTER_BUFFER;
0238 
0239     // Make sure no `performAction` loop caused the item to get out of view
0240     Q_ASSERT(m_State == State::VISIBLE);
0241 
0242     // When the delegate fails to load (or there is none), there is nothing to do
0243     if (metadata()->viewTracker()->state() == StateTracker::ViewItem::State::FAILED)
0244         return false;
0245 
0246     Q_ASSERT(metadata()->viewTracker()->state() == StateTracker::ViewItem::State::BUFFER);
0247 
0248     metadata() << IndexMetadata::ViewAction::ENTER_VIEW;
0249 
0250     // Make sure no `performAction` loop caused the item to get out of view
0251     Q_ASSERT(m_State == State::VISIBLE);
0252 
0253     metadata()->viewport()->s_ptr->updateGeometry(metadata());
0254 
0255     // For some reason creating the visual element failed, this can and will
0256     // happen and need to be recovered from.
0257     if (metadata()->viewTracker()->state() == ViewItem::State::FAILED) {
0258         metadata() << IndexMetadata::ViewAction::LEAVE_BUFFER;
0259 
0260         // Make sure no `performAction` loop caused the item to get out of view
0261         Q_ASSERT(m_State == State::VISIBLE);
0262 
0263         metadata()->setViewTracker(nullptr);
0264     }
0265 
0266     //FIXME do this less often
0267     // This has to be done after `setViewTracker` and performAction
0268     if (down() && down()->metadata()->isValid())
0269         metadata()->viewport()->s_ptr->notifyInsert(down()->metadata());
0270 
0271     // Make sure no `performAction` loop caused the item to get out of view
0272     Q_ASSERT(m_State == State::VISIBLE);
0273 
0274     // Update the edges
0275     if (m_State == State::VISIBLE) {
0276         auto first = q_ptr->edges(IndexMetadata::EdgeType::VISIBLE)->getEdge(Qt::TopEdge);
0277         auto prev  = up();
0278         auto next  = down();
0279         auto last  = q_ptr->edges(IndexMetadata::EdgeType::VISIBLE)->getEdge(Qt::BottomEdge);
0280 
0281         // Make sure the geometry is up to data
0282         metadata()->decoratedGeometry();
0283         Q_ASSERT(metadata()->isValid());
0284 
0285         if (first == next) {
0286             Q_ASSERT((!up()) || (up()->metadata()->modelTracker()->m_State != State::VISIBLE));
0287             q_ptr->setEdge(IndexMetadata::EdgeType::VISIBLE, this, Qt::TopEdge);
0288         }
0289 
0290         if (prev == last) {
0291             Q_ASSERT((!down()) || (down()->metadata()->modelTracker()->m_State != State::VISIBLE));
0292             q_ptr->setEdge(IndexMetadata::EdgeType::VISIBLE, this, Qt::BottomEdge);
0293         }
0294 
0295     }
0296     else
0297         Q_ASSERT(false); //TODO handle failed elements
0298 
0299     //d_ptr->_test_validate_geometry_cache(); //TODO THIS_COMMIT
0300     //Q_ASSERT(metadata()->isInSync()); //TODO THIS_COMMIT
0301 
0302     _DO_TEST(_test_validateViewport, q_ptr)
0303 
0304     return true;
0305 }
0306 
0307 bool StateTracker::ModelItem::hide()
0308 {
0309     if (!metadata()->viewTracker())
0310         return true;
0311 
0312     // This needs more reflection, is using "visible" instead of freeing
0313     // memory a good idea?
0314     //item->setVisible(false);
0315     metadata() << IndexMetadata::ViewAction::LEAVE_BUFFER;
0316 
0317     return true;
0318 }
0319 
0320 bool StateTracker::ModelItem::remove2()
0321 {
0322     if (!metadata()->viewTracker())
0323         return true;
0324 
0325     Q_ASSERT(metadata()->viewTracker()->state() != StateTracker::ViewItem::State::POOLED  );
0326     Q_ASSERT(metadata()->viewTracker()->state() != StateTracker::ViewItem::State::POOLING );
0327     Q_ASSERT(metadata()->viewTracker()->state() != StateTracker::ViewItem::State::DANGLING);
0328 
0329     // Move the item lifecycle forward
0330     while (metadata()->viewTracker()->state() != StateTracker::ViewItem::State::POOLED
0331         && metadata()->viewTracker()->state() != StateTracker::ViewItem::State::DANGLING)
0332         metadata() << IndexMetadata::ViewAction::DETACH;
0333 
0334     // It should still exists, it may crash otherwise, so make sure early
0335     Q_ASSERT(metadata()->viewTracker()->state() == StateTracker::ViewItem::State::POOLED
0336         || metadata()->viewTracker()->state() == StateTracker::ViewItem::State::DANGLING
0337     );
0338 
0339     metadata()->setViewTracker(nullptr);
0340 
0341     return true;
0342 }
0343 
0344 bool StateTracker::ModelItem::attach()
0345 {
0346     Q_ASSERT(m_State != State::VISIBLE && !metadata()->viewTracker());
0347     _DO_TEST(_test_validate_edges_simple, q_ptr)
0348 
0349     //TODO For now the buffer isn't fully implemented, so items always get
0350     // shown when attached.
0351 
0352     //FIXME this ain't correct
0353     return metadata() << IndexMetadata::LoadAction::SHOW;
0354 }
0355 
0356 bool StateTracker::ModelItem::detach()
0357 {
0358     const auto children = allLoadedChildren();
0359 
0360     // First, detach any remaining children
0361     for (auto i : qAsConst(children)) {
0362         i->metadata()
0363             << IndexMetadata::LoadAction::HIDE
0364             << IndexMetadata::LoadAction::DETACH;
0365     }
0366 
0367     remove2();
0368     StateTracker::Index::remove();
0369 
0370     metadata()->viewport()->s_ptr->refreshVisible();
0371 
0372     Q_ASSERT(!loadedChildrenCount() && ((!parent()) || !parent()->childrenLookup(index())));
0373     Q_ASSERT(!metadata()->viewTracker());
0374 
0375     return true;
0376 }
0377 
0378 bool StateTracker::ModelItem::refresh()
0379 {
0380     metadata()->viewport()->s_ptr->updateGeometry(metadata());
0381 
0382     for (auto i = firstChild(); i; i = i->nextSibling()) {
0383         Q_ASSERT(i != this);
0384         i->metadata() << IndexMetadata::LoadAction::UPDATE;
0385 
0386         if (i == lastChild())
0387             break;
0388     }
0389 
0390     // If this isn't true then the state machine is broken
0391     Q_ASSERT(metadata()->viewTracker());
0392 
0393     return true;
0394 }
0395 
0396 bool StateTracker::ModelItem::move()
0397 {
0398     //FIXME Currently this is O(N^2) because refreshVisible does it too
0399 
0400     // Propagate to all children
0401     for (auto i = firstChild(); i; i = i->nextSibling()) {
0402         Q_ASSERT(i != this);
0403         i->metadata() << IndexMetadata::LoadAction::MOVE;
0404 
0405         if (i == lastChild())
0406             break;
0407     }
0408 
0409     if (metadata()->viewTracker()) {
0410         metadata() << IndexMetadata::ViewAction::MOVE;
0411         //Q_ASSERT(metadata()->isInSync());//TODO THIS_COMMIT
0412     }
0413 
0414     Q_ASSERT(metadata()->isValid());
0415 
0416     return true;
0417 }
0418 
0419 bool StateTracker::ModelItem::destroy()
0420 {
0421     Q_ASSERT((!q_ptr) || parent() || this == q_ptr->root());
0422     Q_ASSERT((!parent()) || parent()->hasChildren(this));
0423 
0424     detach();
0425 
0426     Q_ASSERT((!metadata()->viewTracker()) && !loadedChildrenCount());
0427 
0428     delete this;
0429     return true;
0430 }
0431 
0432 bool StateTracker::ModelItem::reset()
0433 {
0434     for (auto i = firstChild(); i; i = i->nextSibling()) {
0435         Q_ASSERT(i);
0436         Q_ASSERT(i != this);
0437         i->metadata() << IndexMetadata::LoadAction::RESET;
0438 
0439         if (i == lastChild())
0440             break;
0441     }
0442 
0443     if (metadata()->viewTracker()) {
0444         metadata()
0445             << IndexMetadata::ViewAction::LEAVE_BUFFER;
0446 
0447         // Move the item lifecycle forward
0448         while (metadata()->viewTracker()->state() != StateTracker::ViewItem::State::POOLED
0449           && metadata()->viewTracker()->state() != StateTracker::ViewItem::State::DANGLING)
0450             metadata() << IndexMetadata::ViewAction::DETACH;
0451 
0452         metadata()->setViewTracker(nullptr);
0453     }
0454 
0455     metadata() << IndexMetadata::GeometryAction::RESET;
0456 
0457     return true;
0458 }