File indexing completed on 2024-04-28 08:33:11

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 "viewport.h"
0019 
0020 // Qt
0021 #include <QtCore/QDebug>
0022 #include <QQmlEngine>
0023 #include <QQmlContext>
0024 
0025 // KQuickItemViews
0026 #include "private/viewport_p.h"
0027 #include "proxies/sizehintproxymodel.h"
0028 #include "private/statetracker/content_p.h"
0029 #include "adapters/modeladapter.h"
0030 #include "adapters/viewportadapter.h"
0031 #include "contextadapterfactory.h"
0032 #include "adapters/contextadapter.h"
0033 #include "private/statetracker/viewitem_p.h"
0034 #include "private/statetracker/model_p.h"
0035 #include "adapters/abstractitemadapter.h"
0036 #include "viewbase.h"
0037 #include "private/indexmetadata_p.h"
0038 #include "private/geostrategyselector_p.h"
0039 
0040 class ViewportPrivate : public QObject
0041 {
0042     Q_OBJECT
0043 public:
0044     QQmlEngine          *m_pEngine       {    nullptr    };
0045     ModelAdapter        *m_pModelAdapter {    nullptr    };
0046     ViewportAdapter     *m_pViewAdapter  {    nullptr    };
0047 
0048     // The viewport rectangle
0049     QRectF m_ViewRect;
0050     QRectF m_UsedRect;
0051 
0052     void updateAvailableEdges();
0053 
0054     Viewport *q_ptr;
0055 
0056 public Q_SLOTS:
0057     void slotModelChanged(QAbstractItemModel* m, QAbstractItemModel* o);
0058     void slotModelAboutToChange(QAbstractItemModel* m, QAbstractItemModel* o);
0059     void slotViewportChanged(const QRectF &viewport);
0060 };
0061 
0062 Viewport::Viewport(ModelAdapter* ma) : QObject(),
0063     s_ptr(new ViewportSync()), d_ptr(new ViewportPrivate())
0064 {
0065     d_ptr->q_ptr           = this;
0066     s_ptr->q_ptr           = this;
0067     d_ptr->m_pModelAdapter = ma;
0068     s_ptr->m_pReflector    = new StateTracker::Content(this);
0069     s_ptr->m_pGeoAdapter   = new GeoStrategySelector(this);
0070 
0071     resize(QRectF { 0.0, 0.0, ma->view()->width(), ma->view()->height() });
0072 
0073     connect(ma, &ModelAdapter::modelChanged,
0074         d_ptr, &ViewportPrivate::slotModelChanged);
0075     connect(ma->view(), &Flickable::viewportChanged,
0076         d_ptr, &ViewportPrivate::slotViewportChanged);
0077     connect(ma, &ModelAdapter::delegateChanged, s_ptr->m_pReflector, [this]() {
0078         s_ptr->m_pReflector->modelTracker()->performAction(
0079             StateTracker::Model::Action::RESET
0080         );
0081     });
0082 
0083     d_ptr->slotModelChanged(ma->rawModel(), nullptr);
0084 
0085     connect(s_ptr->m_pReflector, &StateTracker::Content::contentChanged,
0086         this, &Viewport::contentChanged);
0087 }
0088 
0089 Viewport::~Viewport()
0090 {
0091     delete s_ptr;
0092     delete d_ptr;
0093 }
0094 
0095 QRectF Viewport::currentRect() const
0096 {
0097     return d_ptr->m_UsedRect;
0098 }
0099 
0100 void ViewportPrivate::slotModelAboutToChange(QAbstractItemModel* m, QAbstractItemModel* o)
0101 {
0102     Q_UNUSED(m)
0103 }
0104 
0105 void ViewportPrivate::slotModelChanged(QAbstractItemModel* m, QAbstractItemModel* o)
0106 {
0107     Q_UNUSED(o)
0108     m_pModelAdapter->view()->setContentY(0);
0109 
0110     q_ptr->s_ptr->m_pReflector->modelTracker()->setModel(m);
0111 
0112     Q_ASSERT(m_pModelAdapter->rawModel() == m);
0113 
0114     q_ptr->s_ptr->m_pGeoAdapter->setModel(m);
0115 
0116     if (m && m_ViewRect.size().isValid() && m_pModelAdapter->delegate()) {
0117         q_ptr->s_ptr->m_pReflector->modelTracker()
0118          << StateTracker::Model::Action::POPULATE
0119          << StateTracker::Model::Action::ENABLE;
0120     }
0121 }
0122 
0123 void ViewportPrivate::slotViewportChanged(const QRectF &viewport)
0124 {
0125 //     Q_ASSERT(viewport.y() == 0); //FIXME I broke it
0126     m_ViewRect = viewport;
0127     m_UsedRect = viewport; //FIXME remove wrong
0128     updateAvailableEdges();
0129     q_ptr->s_ptr->m_pReflector->modelTracker() << StateTracker::Model::Action::MOVE;
0130 }
0131 
0132 ModelAdapter *Viewport::modelAdapter() const
0133 {
0134     return d_ptr->m_pModelAdapter;
0135 }
0136 
0137 QSizeF Viewport::size() const
0138 {
0139     return d_ptr->m_UsedRect.size();
0140 }
0141 
0142 QPointF Viewport::position() const
0143 {
0144     return d_ptr->m_UsedRect.topLeft();
0145 }
0146 
0147 QSizeF Viewport::totalSize() const
0148 {
0149     if ((!d_ptr->m_pModelAdapter->delegate()) || !d_ptr->m_pModelAdapter->rawModel())
0150         return {0.0, 0.0};
0151 
0152     return {}; //TODO
0153 }
0154 
0155 void Viewport::setItemFactory(ViewBase::ItemFactoryBase *factory)
0156 {
0157     s_ptr->m_fFactory = ([this, factory]() -> AbstractItemAdapter* {
0158         return factory->create(this);
0159     });
0160 }
0161 
0162 Qt::Edges Viewport::availableEdges() const
0163 {
0164     return s_ptr->m_pReflector->availableEdges(
0165         IndexMetadata::EdgeType::FREE
0166     );
0167 }
0168 
0169 void ViewportPrivate::updateAvailableEdges()
0170 {
0171     if (q_ptr->s_ptr->m_pReflector->modelTracker()->state() == StateTracker::Model::State::RESETING)
0172         return; //TODO it needs another state machine to get rid of the `if`
0173 
0174     if (!q_ptr->s_ptr->m_pReflector->modelTracker()->modelCandidate())
0175         return;
0176 
0177     Qt::Edges available;
0178 
0179     auto v = m_pModelAdapter->view();
0180 
0181     auto tve = q_ptr->s_ptr->m_pReflector->getEdge(
0182         IndexMetadata::EdgeType::VISIBLE, Qt::TopEdge
0183     );
0184 
0185     Q_ASSERT((!tve) || (!tve->up()) || (!tve->up()->isVisible()));
0186 
0187     auto bve = q_ptr->s_ptr->m_pReflector->getEdge(
0188         IndexMetadata::EdgeType::VISIBLE, Qt::BottomEdge
0189     );
0190 
0191     Q_ASSERT((!bve) || (!bve->down()) || (!bve->down()->isVisible()));
0192 
0193     // If they don't have a valid size, then there is a bug elsewhere
0194     Q_ASSERT((!tve) || tve->isValid());
0195     Q_ASSERT((!bve) || tve->isValid());
0196 
0197     // Do not attempt to load the geometry yet, let the loading code do it later
0198     const bool tveValid = tve && tve->isValid();
0199     const bool bveValid = bve && bve->isValid();
0200 
0201     // Given 42x0 sized item are possible. However just "fixing" this by adding
0202     // a minimum size wont help because it will trigger the out of sync view
0203     // correction in an infinite loop.
0204     QRectF tvg(tve?tve->decoratedGeometry():QRectF()), bvg(bve?bve->decoratedGeometry():QRectF());
0205 
0206     const auto fixedIntersect = [](bool valid, QRectF& vp, QRectF& geo) -> bool {
0207         Q_UNUSED(valid) //TODO
0208         return vp.intersects(geo) || (
0209             geo.y() >= vp.y() && geo.width() == 0 && geo.y() <= vp.y() + vp.height()
0210         ) || (
0211             geo.height() == 0 && geo.width() > 0  && geo.x() <= vp.x() + vp.width()
0212             && geo.x() >= vp.x()
0213         );
0214     };
0215 
0216     QRectF vp = m_ViewRect;
0217 
0218     // Add an extra pixel to the height to prevent off-by-one where the view is
0219     // perfectly full and can't scroll any more (and thus load the next item)
0220     vp.setHeight(vp.height()+1.0);
0221 
0222     if ((!tve) || (fixedIntersect(tveValid, vp, tvg) && tvg.y() > 0))
0223         available |= Qt::TopEdge;
0224 
0225     if ((!bve) || fixedIntersect(bveValid, vp, bvg))
0226         available |= Qt::BottomEdge;
0227 
0228     q_ptr->s_ptr->m_pReflector->setAvailableEdges(
0229         available, IndexMetadata::EdgeType::FREE
0230     );
0231 
0232     q_ptr->s_ptr->m_pReflector->setAvailableEdges(
0233         available, IndexMetadata::EdgeType::VISIBLE
0234     );
0235 
0236 //     Q_ASSERT((~hasInvisible)&available == available || !available);
0237 //     m_pReflector->setAvailableEdges(
0238 //         (~hasInvisible)&15, IndexMetadata::EdgeType::VISIBLE
0239 //     );
0240 
0241     q_ptr->s_ptr->m_pReflector->modelTracker() << StateTracker::Model::Action::TRIM;
0242 
0243     const auto oldBve(bve), oldTve(tve);
0244 
0245     bve = q_ptr->s_ptr->m_pReflector->getEdge(
0246         IndexMetadata::EdgeType::VISIBLE, Qt::BottomEdge
0247     );
0248 
0249     //BEGIN test
0250     tve = q_ptr->s_ptr->m_pReflector->getEdge(
0251         IndexMetadata::EdgeType::VISIBLE, Qt::TopEdge
0252     );
0253 
0254     Q_ASSERT((!tve) || (!tve->up()) || (!tve->up()->isVisible()));
0255 
0256 
0257     Q_ASSERT((!bve) || (!bve->down()) || (!bve->down()->isVisible()));
0258     //END test
0259 
0260     // Size is normal as the state as not converged yet
0261     Q_ASSERT((!bve) || (
0262         bve->geometryTracker()->state() == StateTracker::Geometry::State::VALID ||
0263         bve->geometryTracker()->state() == StateTracker::Geometry::State::SIZE)
0264     );
0265 
0266     // Resize the contend height, it has to be done after the geometry has been
0267     // updated.
0268     if (bve && q_ptr->s_ptr->m_pGeoAdapter->capabilities() & GeometryAdapter::Capabilities::TRACKS_QQUICKITEM_GEOMETRY) {
0269 
0270         const auto geo = bve->decoratedGeometry();
0271 
0272         v->contentItem()->setHeight(std::max(
0273             geo.y()+geo.height(), v->height()
0274         ));
0275 
0276         emit v->contentHeightChanged( v->contentItem()->height() );
0277     }
0278 
0279     if (oldTve != tve || oldBve != bve)
0280         emit q_ptr->cornerChanged();
0281 }
0282 
0283 void ViewportSync::geometryUpdated(IndexMetadata *item)
0284 {
0285     if (m_pReflector->modelTracker()->state() == StateTracker::Model::State::RESETING)
0286         return; //TODO it needs another state machine to get rid of the `if`
0287 
0288     //TODO assert if the size hints don't match reality
0289 
0290     // This will recompute the geometry
0291     item->decoratedGeometry();
0292 
0293     if (m_pGeoAdapter->capabilities() & GeometryAdapter::Capabilities::TRACKS_QQUICKITEM_GEOMETRY)
0294         q_ptr->d_ptr->updateAvailableEdges();
0295 }
0296 
0297 void ViewportSync::updateGeometry(IndexMetadata* item)
0298 {
0299     if (m_pGeoAdapter->capabilities() & GeometryAdapter::Capabilities::TRACKS_QQUICKITEM_GEOMETRY)
0300         item->sizeHint();
0301 
0302     if (auto i = item->down())
0303         notifyInsert(i);
0304 
0305     q_ptr->d_ptr->updateAvailableEdges();
0306 
0307     refreshVisible();
0308     //notifyInsert(item->down());
0309 
0310 }
0311 
0312 // When the QModelIndex role change
0313 void ViewportSync::notifyChange(IndexMetadata* item)
0314 {
0315     item << IndexMetadata::GeometryAction::MODIFY;
0316 }
0317 
0318 void ViewportSync::notifyRemoval(IndexMetadata* item)
0319 {
0320     Q_UNUSED(item)
0321     if (m_pReflector->modelTracker()->state() == StateTracker::Model::State::RESETING)
0322         return; //TODO it needs another state machine to get rid of the `if`
0323 
0324 //     auto tve = m_pReflector->getEdge(
0325 //         IndexMetadata::EdgeType::VISIBLE, Qt::TopEdge
0326 //     );
0327 
0328 //     auto bve = q_ptr->d_ptr->m_pReflector->getEdge(
0329 //         IndexMetadata::EdgeType::VISIBLE, Qt::BottomEdge
0330 //     );
0331 //     Q_ASSERT(item != bve); //TODO
0332 
0333 //     //FIXME this is horrible
0334 //     while(auto next = item->down()) { //FIXME dead code
0335 //         if (next->m_State.state() != StateTracker::Geometry::State::VALID ||
0336 //           next->m_State.state() != StateTracker::Geometry::State::POSITION)
0337 //             break;
0338 //
0339 //         next->m_State.performAction(
0340 //             GeometryAction::MOVE, nullptr, nullptr
0341 //         );
0342 //
0343 //         Q_ASSERT(next != bve); //TODO
0344 //
0345 //         Q_ASSERT(next->m_State.state() != StateTracker::Geometry::State::VALID);
0346 //     }
0347 //
0348 //     refreshVisible();
0349 //
0350 //     q_ptr->d_ptr->updateAvailableEdges();
0351 }
0352 
0353 //TODO temporary hack to avoid having to implement a complex way to move the
0354 // limited number of loaded elements. Given once trimming is enabled, the
0355 // number of visible items should be fairely low/constant, it is a good enough
0356 // temporary solution.
0357 void ViewportSync::refreshVisible()
0358 {
0359     if (m_pReflector->modelTracker()->state() == StateTracker::Model::State::RESETING)
0360         return; //TODO it needs another state machine to get rid of the `if`
0361 
0362     //TODO eventually move to a relative origin so moving an item to the top
0363     // doesn't need to move everything
0364 
0365     IndexMetadata *item = m_pReflector->getEdge(
0366         IndexMetadata::EdgeType::VISIBLE, Qt::TopEdge
0367     );
0368 
0369     if (!item)
0370         return;
0371 
0372     Q_ASSERT((!item->up()) || !item->up()->isVisible());
0373 
0374     auto bve = m_pReflector->getEdge(
0375         IndexMetadata::EdgeType::VISIBLE, Qt::BottomEdge
0376     );
0377 
0378     auto prev = item->up();
0379 
0380     // First, make sure the previous elem has a valid size. If, for example,
0381     // rowsMoved moves a previously unloaded item to the front, this information
0382     // will be lost.
0383     if (prev && !prev->isValid()) {
0384 
0385         // This is the slow path, it can be /very/ slow. Possible mitigation
0386         // include adding more lower level methods to never lose track of the
0387         // list state, but this makes everything more (too) complex. Another
0388         // better solution is more aggressive unloading to the list becomes
0389         // smaller.
0390         if (!prev->isTopItem()) {
0391             qDebug() << "Slow path";
0392             //FIXME this is slow
0393             auto i = prev;
0394             while((i = i->up()) && i && !i->isValid());
0395             Q_ASSERT(i);
0396 
0397             while ((i = i->down()) != item)
0398                 i->decoratedGeometry();
0399         }
0400         else
0401             prev->setPosition({0.0, 0.0});
0402     }
0403 
0404     const bool hasSingleItem = item == bve;
0405 
0406     do {
0407         item->sizeHint();
0408 
0409         Q_ASSERT(item->geometryTracker()->state() != StateTracker::Geometry::State::INIT);
0410         Q_ASSERT(item->geometryTracker()->state() != StateTracker::Geometry::State::POSITION);
0411 
0412         if (prev) {
0413             //FIXME this isn't ok, it needs to take into account the point size
0414             //it could by multiple pixels
0415             item->setPosition(prev->decoratedGeometry().bottomLeft());
0416         }
0417 
0418         item->sizeHint();
0419         Q_ASSERT(item->isVisible());
0420         Q_ASSERT(item->isValid());
0421         //FIXME As of 0.1, this still fails from time to time
0422         //Q_ASSERT(item->isInSync());//TODO THIS_COMMIT
0423 
0424         // This `performAction` exists to recover from runtime failures
0425         if (!item->isInSync())
0426             item << IndexMetadata::LoadAction::MOVE;
0427 
0428     } while((!hasSingleItem) && item->up() != bve && (item = item->down()));
0429 }
0430 
0431 void ViewportSync::notifyInsert(IndexMetadata* item)
0432 {
0433     using GeoState = StateTracker::Geometry::State;
0434     Q_ASSERT(item);
0435 
0436     if (m_pReflector->modelTracker()->state() == StateTracker::Model::State::RESETING)
0437         return; //TODO it needs another state machine to get rid of the `if`
0438 
0439     if (!item)
0440         return;
0441 
0442     const bool needsPosition = item->geometryTracker()->state() == GeoState::INIT ||
0443       item->geometryTracker()->state() == GeoState::SIZE;
0444 
0445     // If the item is new and is inserted near valid items, skip some back and
0446     // forth and set the position now.
0447     if (needsPosition && item->up() && item->up()->geometryTracker()->state() ==  GeoState::VALID) {
0448         item->setPosition(item->up()->decoratedGeometry().bottomLeft());
0449     }
0450 
0451     // If the item is inserted in front, set the position
0452     if (item->isTopItem()) {
0453         item->setPosition({0.0, 0.0});
0454     }
0455 
0456     auto bve = m_pReflector->getEdge(
0457         IndexMetadata::EdgeType::VISIBLE, Qt::BottomEdge
0458     );
0459 
0460     //FIXME this is also horrible
0461     do {
0462         if (item == bve) {
0463             item << IndexMetadata::GeometryAction::MOVE;
0464             break;
0465         }
0466 
0467         if (!item->isValid() &&
0468           item->geometryTracker()->state() != GeoState::POSITION) {
0469             break;
0470         }
0471 
0472         item << IndexMetadata::GeometryAction::MOVE;
0473     } while((item = item->down()));
0474 
0475     refreshVisible();
0476 
0477     q_ptr->d_ptr->updateAvailableEdges();
0478 }
0479 
0480 void Viewport::resize(const QRectF& rect)
0481 {
0482     if (s_ptr->m_pReflector->modelTracker()->state() == StateTracker::Model::State::RESETING)
0483         return; //TODO it needs another state machine to get rid of the `if`
0484 
0485     const bool wasValid = d_ptr->m_ViewRect.size().isValid();
0486 
0487     Q_ASSERT(rect.x() == 0);
0488 
0489     // The {x, y} may not be at {0, 0}, but given it is a relative viewport,
0490     // then the content doesn't care about where it is on the screen.
0491     d_ptr->m_ViewRect = rect;
0492     Q_ASSERT(rect.y() == 0);
0493 
0494     // For now ignore the case where the content is smaller, it doesn't change
0495     // anything. This could eventually change
0496     d_ptr->m_UsedRect.setSize(rect.size()); //FIXME remove, wrong
0497 
0498     s_ptr->refreshVisible();
0499 
0500     d_ptr->updateAvailableEdges();
0501 
0502     if ((!d_ptr->m_pModelAdapter) || (!d_ptr->m_pModelAdapter->rawModel()))
0503         return;
0504 
0505     if (!d_ptr->m_pModelAdapter->delegate())
0506         return;
0507 
0508     if (!rect.isValid())
0509         return;
0510 
0511     // Refresh the content
0512     if (!wasValid)
0513         s_ptr->m_pReflector->modelTracker()
0514             << StateTracker::Model::Action::POPULATE
0515             << StateTracker::Model::Action::ENABLE;
0516     else {
0517         //FIXME make sure the state machine handle the lack of delegate properly
0518         s_ptr->m_pReflector->modelTracker() << StateTracker::Model::Action::MOVE;
0519     }
0520 }
0521 
0522 IndexMetadata *ViewportSync::metadataForIndex(const QModelIndex& idx) const
0523 {
0524     return m_pReflector->metadataForIndex(idx);
0525 }
0526 
0527 QQmlEngine *ViewportSync::engine()
0528 {
0529     if (!m_pEngine)
0530         m_pEngine = q_ptr->modelAdapter()->view()->rootContext()->engine();
0531 
0532     return m_pEngine;
0533 }
0534 
0535 QQmlComponent *ViewportSync::component()
0536 {
0537     engine();
0538 
0539     m_pComponent = new QQmlComponent(m_pEngine);
0540     m_pComponent->setData("import QtQuick 2.4; Item {property QtObject content: null;}", {});
0541 
0542     return m_pComponent;
0543 }
0544 
0545 GeometryAdapter *Viewport::geometryAdapter() const
0546 {
0547     return s_ptr->m_pGeoAdapter;
0548 }
0549 
0550 void Viewport::setGeometryAdapter(GeometryAdapter *a)
0551 {
0552     s_ptr->m_pGeoAdapter->setCurrentAdapter(a);
0553 }
0554 
0555 QModelIndex Viewport::indexAt(const QPoint &point) const
0556 {
0557     return {}; //TODO
0558 }
0559 
0560 QModelIndex Viewport::indexAt(Qt::Corner corner) const
0561 {
0562     // multi column isn't supported yet, so it is close enough for now
0563     switch (corner) {
0564         case Qt::TopLeftCorner:
0565         case Qt::TopRightCorner:
0566             return indexAt(Qt::TopEdge);
0567         case Qt::BottomLeftCorner:
0568         case Qt::BottomRightCorner:
0569             return indexAt(Qt::BottomEdge);
0570     }
0571 
0572     return {};
0573 }
0574 
0575 QModelIndex Viewport::indexAt(Qt::Edge edge) const
0576 {
0577     if (auto tve = s_ptr->m_pReflector->getEdge(IndexMetadata::EdgeType::VISIBLE, edge))
0578         return tve->index();
0579 
0580     return {};
0581 }
0582 
0583 QRectF Viewport::itemRect(const QModelIndex& i) const
0584 {
0585     if (auto md = s_ptr->m_pReflector->metadataForIndex(i))
0586         return md->decoratedGeometry();
0587 
0588     //TODO load it
0589 
0590     return {};
0591 }
0592 
0593 #include <viewport.moc>