File indexing completed on 2024-03-24 04:44:23
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>