diff --git a/src/singlemodelviewbase.cpp b/src/singlemodelviewbase.cpp index 6ba0857..efe7b27 100644 --- a/src/singlemodelviewbase.cpp +++ b/src/singlemodelviewbase.cpp @@ -1,199 +1,230 @@ /*************************************************************************** * Copyright (C) 2017 by Emmanuel Lepage Vallee * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ #include "singlemodelviewbase.h" // Qt #include // KQuickItemViews #include #include #include -#include +#include "viewport.h" #include "private/viewport_p.h" #include "private/geostrategyselector_p.h" class SingleModelViewBasePrivate { public: bool m_IsSortingEnabled { false }; ModelAdapter *m_pModelAdapter { nullptr }; SingleModelViewBase* q_ptr; }; SingleModelViewBase::SingleModelViewBase(ItemFactoryBase *factory, QQuickItem* parent) : ViewBase(parent), d_ptr(new SingleModelViewBasePrivate()) { d_ptr->q_ptr = this; d_ptr->m_pModelAdapter = new ModelAdapter(this); addModelAdapter(d_ptr->m_pModelAdapter); - d_ptr->m_pModelAdapter->viewports().first()->setItemFactory(factory); + auto vp = d_ptr->m_pModelAdapter->viewports().first(); + vp->setItemFactory(factory); auto sm = d_ptr->m_pModelAdapter->selectionAdapter(); // Ok, connecting signals to signals is not a very good idea, I am lazy connect(sm, &SelectionAdapter::currentIndexChanged, this, &SingleModelViewBase::currentIndexChanged); connect(sm, &SelectionAdapter::selectionModelChanged, this, &SingleModelViewBase::selectionModelChanged); connect(d_ptr->m_pModelAdapter, &ModelAdapter::modelAboutToChange, this, &SingleModelViewBase::applyModelChanges); + connect(vp, &Viewport::cornerChanged, + this, &SingleModelViewBase::cornerChanged); } SingleModelViewBase::~SingleModelViewBase() { delete d_ptr; } QQmlComponent* SingleModelViewBase::highlight() const { return d_ptr->m_pModelAdapter->selectionAdapter()->highlight(); } void SingleModelViewBase::setHighlight(QQmlComponent* h) { d_ptr->m_pModelAdapter->selectionAdapter()->setHighlight(h); } void SingleModelViewBase::setDelegate(QQmlComponent* delegate) { d_ptr->m_pModelAdapter->setDelegate(delegate); emit delegateChanged(delegate); refresh(); } QQmlComponent* SingleModelViewBase::delegate() const { return d_ptr->m_pModelAdapter->delegate(); } QSharedPointer SingleModelViewBase::selectionModel() const { return d_ptr->m_pModelAdapter->selectionAdapter()->selectionModel(); } QVariant SingleModelViewBase::model() const { return d_ptr->m_pModelAdapter->model(); } void SingleModelViewBase::setModel(const QVariant& m) { d_ptr->m_pModelAdapter->setModel(m); emit modelChanged(); } void SingleModelViewBase::setSelectionModel(QSharedPointer m) { d_ptr->m_pModelAdapter->selectionAdapter()->setSelectionModel(m); emit selectionModelChanged(); } QAbstractItemModel *SingleModelViewBase::rawModel() const { return d_ptr->m_pModelAdapter->rawModel(); } void SingleModelViewBase::applyModelChanges(QAbstractItemModel* m) { if (d_ptr->m_IsSortingEnabled && m) { m->sort(0); } } bool SingleModelViewBase::isDelegateSizeForced() const { return d_ptr->m_pModelAdapter->viewports().constFirst()->s_ptr-> m_pGeoAdapter->isSizeForced(); } void SingleModelViewBase::setDelegateSizeForced(bool f) { d_ptr->m_pModelAdapter->viewports().constFirst()->s_ptr-> m_pGeoAdapter->setSizeForced(f); } bool SingleModelViewBase::isSortingEnabled() const { return d_ptr->m_IsSortingEnabled; } void SingleModelViewBase::setSortingEnabled(bool val) { d_ptr->m_IsSortingEnabled = val; if (d_ptr->m_IsSortingEnabled && rawModel()) { rawModel()->sort(0); } } QModelIndex SingleModelViewBase::currentIndex() const { return selectionModel()->currentIndex(); } void SingleModelViewBase::setCurrentIndex(const QModelIndex& index, QItemSelectionModel::SelectionFlags f) { selectionModel()->setCurrentIndex(index, f); } bool SingleModelViewBase::hasUniformRowHeight() const { return d_ptr->m_pModelAdapter->viewports().constFirst()->s_ptr-> m_pGeoAdapter->capabilities() & GeometryAdapter::Capabilities::HAS_UNIFORM_HEIGHT; } void SingleModelViewBase::setUniformRowHeight(bool value) { Q_UNUSED(value) //d_ptr->m_pModelAdapter->setUniformRowHeight(value); } bool SingleModelViewBase::hasUniformColumnWidth() const { return d_ptr->m_pModelAdapter->viewports().constFirst()->s_ptr-> m_pGeoAdapter->capabilities() & GeometryAdapter::Capabilities::HAS_UNIFORM_WIDTH; } void SingleModelViewBase::setUniformColumnColumnWidth(bool value) { Q_UNUSED(value) //d_ptr->m_pModelAdapter->setUniformColumnColumnWidth(value); } void SingleModelViewBase::moveTo(Qt::Edge e) { QTimer::singleShot(0, [this, e]() { //HACK This need the viewportAdapter to be optimized switch(e) { case Qt::TopEdge: setCurrentY(0); break; case Qt::BottomEdge: { int y = currentY(); // Keep loading until it doesn't load anything else do { setCurrentY(999999); } while (currentY() > y && (y = currentY())); } + case Qt::LeftEdge: + case Qt::RightEdge: + break; //TODO } }); } + +QModelIndex SingleModelViewBase::indexAt(const QPoint & point) const +{ + return d_ptr->m_pModelAdapter->viewports().first()->indexAt(point); +} + +QModelIndex SingleModelViewBase::topLeft() const +{ + return d_ptr->m_pModelAdapter->viewports().first()->indexAt(Qt::TopLeftCorner); +} + +QModelIndex SingleModelViewBase::topRight() const +{ + return d_ptr->m_pModelAdapter->viewports().first()->indexAt(Qt::TopRightCorner); +} + +QModelIndex SingleModelViewBase::bottomLeft() const +{ + return d_ptr->m_pModelAdapter->viewports().first()->indexAt(Qt::BottomLeftCorner); +} + +QModelIndex SingleModelViewBase::bottomRight() const +{ + return d_ptr->m_pModelAdapter->viewports().first()->indexAt(Qt::BottomRightCorner); +} diff --git a/src/singlemodelviewbase.h b/src/singlemodelviewbase.h index 53391ba..0a83202 100644 --- a/src/singlemodelviewbase.h +++ b/src/singlemodelviewbase.h @@ -1,130 +1,143 @@ /*************************************************************************** * Copyright (C) 2017 by Emmanuel Lepage Vallee * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ #ifndef SINGLEMODELVIEWBASE_H #define SINGLEMODELVIEWBASE_H #include class SingleModelViewBasePrivate; /** * Random code to get close to drop-in compatibility with both QML and QtWidgets * views. * * This library * tries to enforce smaller components with well defined scope. But that makes * its API larger and more complex. This class helps to collapse all the * concepts into a single thing. This removes some flexibility, but allows * the widgets to be (almost) drop-in replacements for the ones provided * by QtQuick2. */ class Q_DECL_EXPORT SingleModelViewBase : public ViewBase { Q_OBJECT public: Q_PROPERTY(QQmlComponent* highlight READ highlight WRITE setHighlight) Q_PROPERTY(QSharedPointer selectionModel READ selectionModel WRITE setSelectionModel NOTIFY selectionModelChanged) Q_PROPERTY(bool sortingEnabled READ isSortingEnabled WRITE setSortingEnabled) Q_PROPERTY(QModelIndex currentIndex READ currentIndex WRITE setCurrentIndex) Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged) Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + // Visible content + Q_PROPERTY(QModelIndex topLeft READ topLeft NOTIFY cornerChanged) + Q_PROPERTY(QModelIndex topRight READ topRight NOTIFY cornerChanged) + Q_PROPERTY(QModelIndex bottomLeft READ bottomLeft NOTIFY cornerChanged) + Q_PROPERTY(QModelIndex bottomRight READ bottomRight NOTIFY cornerChanged) + /** * Whether to use the delegate implicit size or always explicitly apply the * size hints explicitly. */ Q_PROPERTY(bool forceDelegateSize READ isDelegateSizeForced WRITE setDelegateSizeForced) /// Assume each hierarchy level have the same height (for performance) Q_PROPERTY(bool uniformRowHeight READ hasUniformRowHeight WRITE setUniformRowHeight) /// Assume each column has the same width (for performance) Q_PROPERTY(bool uniformColumnWidth READ hasUniformColumnWidth WRITE setUniformColumnColumnWidth) /** * It is usually recommanded to use the template constructor unless there is * extra logic to be executed when an item is created. */ explicit SingleModelViewBase(ItemFactoryBase *factory, QQuickItem* parent = nullptr); template SingleModelViewBase(ItemFactory *b, QQuickItem* parent = nullptr) : SingleModelViewBase((ItemFactoryBase*) b, parent) {} virtual ~SingleModelViewBase(); QQmlComponent* highlight() const; void setHighlight(QQmlComponent* h); QVariant model() const; void setModel(const QVariant& m); void setDelegate(QQmlComponent* delegate); QQmlComponent* delegate() const; QSharedPointer selectionModel() const; void setSelectionModel(QSharedPointer m); bool isDelegateSizeForced() const; void setDelegateSizeForced(bool f); QModelIndex currentIndex() const; Q_INVOKABLE void setCurrentIndex(const QModelIndex& index, QItemSelectionModel::SelectionFlags f = QItemSelectionModel::ClearAndSelect ); bool isSortingEnabled() const; void setSortingEnabled(bool val); bool hasUniformRowHeight() const; void setUniformRowHeight(bool value); bool hasUniformColumnWidth() const; void setUniformColumnColumnWidth(bool value); + QModelIndex topLeft () const; + QModelIndex topRight () const; + QModelIndex bottomLeft () const; + QModelIndex bottomRight() const; + Q_INVOKABLE void moveTo(Qt::Edge e); + Q_INVOKABLE QModelIndex indexAt(const QPoint & point) const; protected Q_SLOTS: /** * Calling `setModel` doesn't do anything that takes effect immediately. * * Rather, it will either wait to the next event loop iteration to make * sure other properties have been applied. If no delegate is set, it will * also avoid loading the model internal representation because that would * be useless anyway. * * Override this method if extra steps are to be taken when replacing the * model. Do not forget to call the superclass method. */ virtual void applyModelChanges(QAbstractItemModel* m); protected: QAbstractItemModel *rawModel() const; Q_SIGNALS: void currentIndexChanged(const QModelIndex& index); void selectionModelChanged() const; void modelChanged(); void delegateChanged(QQmlComponent* delegate); + void cornerChanged(); // virtual void countChanged() override final; private: SingleModelViewBasePrivate* d_ptr; }; #endif diff --git a/src/viewport.cpp b/src/viewport.cpp index 6791dc9..d008e19 100644 --- a/src/viewport.cpp +++ b/src/viewport.cpp @@ -1,550 +1,583 @@ /*************************************************************************** * Copyright (C) 2018 by Emmanuel Lepage Vallee * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ #include "viewport.h" // Qt #include #include #include // KQuickItemViews #include "private/viewport_p.h" #include "proxies/sizehintproxymodel.h" #include "private/statetracker/content_p.h" #include "adapters/modeladapter.h" #include "adapters/viewportadapter.h" #include "contextadapterfactory.h" #include "adapters/contextadapter.h" #include "private/statetracker/viewitem_p.h" #include "private/statetracker/model_p.h" #include "adapters/abstractitemadapter.h" #include "viewbase.h" #include "private/indexmetadata_p.h" #include "private/geostrategyselector_p.h" class ViewportPrivate : public QObject { Q_OBJECT public: QQmlEngine *m_pEngine { nullptr }; ModelAdapter *m_pModelAdapter { nullptr }; ViewportAdapter *m_pViewAdapter { nullptr }; // The viewport rectangle QRectF m_ViewRect; QRectF m_UsedRect; void updateAvailableEdges(); Viewport *q_ptr; public Q_SLOTS: void slotModelChanged(QAbstractItemModel* m, QAbstractItemModel* o); void slotModelAboutToChange(QAbstractItemModel* m, QAbstractItemModel* o); void slotViewportChanged(const QRectF &viewport); }; Viewport::Viewport(ModelAdapter* ma) : QObject(), s_ptr(new ViewportSync()), d_ptr(new ViewportPrivate()) { d_ptr->q_ptr = this; s_ptr->q_ptr = this; d_ptr->m_pModelAdapter = ma; s_ptr->m_pReflector = new StateTracker::Content(this); s_ptr->m_pGeoAdapter = new GeoStrategySelector(this); resize(QRectF { 0.0, 0.0, ma->view()->width(), ma->view()->height() }); connect(ma, &ModelAdapter::modelChanged, d_ptr, &ViewportPrivate::slotModelChanged); connect(ma->view(), &Flickable::viewportChanged, d_ptr, &ViewportPrivate::slotViewportChanged); connect(ma, &ModelAdapter::delegateChanged, s_ptr->m_pReflector, [this]() { s_ptr->m_pReflector->modelTracker()->performAction( StateTracker::Model::Action::RESET ); }); d_ptr->slotModelChanged(ma->rawModel(), nullptr); connect(s_ptr->m_pReflector, &StateTracker::Content::contentChanged, this, &Viewport::contentChanged); } Viewport::~Viewport() { delete s_ptr; delete d_ptr; } QRectF Viewport::currentRect() const { return d_ptr->m_UsedRect; } void ViewportPrivate::slotModelAboutToChange(QAbstractItemModel* m, QAbstractItemModel* o) { Q_UNUSED(m) } void ViewportPrivate::slotModelChanged(QAbstractItemModel* m, QAbstractItemModel* o) { Q_UNUSED(o) m_pModelAdapter->view()->setCurrentY(0); q_ptr->s_ptr->m_pReflector->modelTracker()->setModel(m); Q_ASSERT(m_pModelAdapter->rawModel() == m); q_ptr->s_ptr->m_pGeoAdapter->setModel(m); if (m && m_ViewRect.size().isValid() && m_pModelAdapter->delegate()) { q_ptr->s_ptr->m_pReflector->modelTracker() << StateTracker::Model::Action::POPULATE << StateTracker::Model::Action::ENABLE; } } void ViewportPrivate::slotViewportChanged(const QRectF &viewport) { // Q_ASSERT(viewport.y() == 0); //FIXME I broke it m_ViewRect = viewport; m_UsedRect = viewport; //FIXME remove wrong updateAvailableEdges(); q_ptr->s_ptr->m_pReflector->modelTracker() << StateTracker::Model::Action::MOVE; } ModelAdapter *Viewport::modelAdapter() const { return d_ptr->m_pModelAdapter; } QSizeF Viewport::size() const { return d_ptr->m_UsedRect.size(); } QPointF Viewport::position() const { return d_ptr->m_UsedRect.topLeft(); } QSizeF Viewport::totalSize() const { if ((!d_ptr->m_pModelAdapter->delegate()) || !d_ptr->m_pModelAdapter->rawModel()) return {0.0, 0.0}; return {}; //TODO } void Viewport::setItemFactory(ViewBase::ItemFactoryBase *factory) { s_ptr->m_fFactory = ([this, factory]() -> AbstractItemAdapter* { return factory->create(this); }); } Qt::Edges Viewport::availableEdges() const { return s_ptr->m_pReflector->availableEdges( IndexMetadata::EdgeType::FREE ); } void ViewportPrivate::updateAvailableEdges() { if (q_ptr->s_ptr->m_pReflector->modelTracker()->state() == StateTracker::Model::State::RESETING) return; //TODO it needs another state machine to get rid of the `if` if (!q_ptr->s_ptr->m_pReflector->modelTracker()->modelCandidate()) return; Qt::Edges available; auto v = m_pModelAdapter->view(); auto tve = q_ptr->s_ptr->m_pReflector->getEdge( IndexMetadata::EdgeType::VISIBLE, Qt::TopEdge ); Q_ASSERT((!tve) || (!tve->up()) || (!tve->up()->isVisible())); auto bve = q_ptr->s_ptr->m_pReflector->getEdge( IndexMetadata::EdgeType::VISIBLE, Qt::BottomEdge ); Q_ASSERT((!bve) || (!bve->down()) || (!bve->down()->isVisible())); // If they don't have a valid size, then there is a bug elsewhere Q_ASSERT((!tve) || tve->isValid()); Q_ASSERT((!bve) || tve->isValid()); // Do not attempt to load the geometry yet, let the loading code do it later const bool tveValid = tve && tve->isValid(); const bool bveValid = bve && bve->isValid(); // Given 42x0 sized item are possible. However just "fixing" this by adding // a minimum size wont help because it will trigger the out of sync view // correction in an infinite loop. QRectF tvg(tve?tve->decoratedGeometry():QRectF()), bvg(bve?bve->decoratedGeometry():QRectF()); const auto fixedIntersect = [](bool valid, QRectF& vp, QRectF& geo) -> bool { Q_UNUSED(valid) //TODO return vp.intersects(geo) || ( geo.y() >= vp.y() && geo.width() == 0 && geo.y() <= vp.y() + vp.height() ) || ( geo.height() == 0 && geo.width() > 0 && geo.x() <= vp.x() + vp.width() && geo.x() >= vp.x() ); }; QRectF vp = m_ViewRect; // Add an extra pixel to the height to prevent off-by-one where the view is // perfectly full and can't scroll any more (and thus load the next item) vp.setHeight(vp.height()+1.0); if ((!tve) || (fixedIntersect(tveValid, vp, tvg) && tvg.y() > 0)) available |= Qt::TopEdge; if ((!bve) || fixedIntersect(bveValid, vp, bvg)) available |= Qt::BottomEdge; q_ptr->s_ptr->m_pReflector->setAvailableEdges( available, IndexMetadata::EdgeType::FREE ); q_ptr->s_ptr->m_pReflector->setAvailableEdges( available, IndexMetadata::EdgeType::VISIBLE ); // Q_ASSERT((~hasInvisible)&available == available || !available); // m_pReflector->setAvailableEdges( // (~hasInvisible)&15, IndexMetadata::EdgeType::VISIBLE // ); q_ptr->s_ptr->m_pReflector->modelTracker() << StateTracker::Model::Action::TRIM; + const auto oldBve(bve), oldTve(tve); + bve = q_ptr->s_ptr->m_pReflector->getEdge( IndexMetadata::EdgeType::VISIBLE, Qt::BottomEdge ); //BEGIN test tve = q_ptr->s_ptr->m_pReflector->getEdge( IndexMetadata::EdgeType::VISIBLE, Qt::TopEdge ); Q_ASSERT((!tve) || (!tve->up()) || (!tve->up()->isVisible())); Q_ASSERT((!bve) || (!bve->down()) || (!bve->down()->isVisible())); //END test // Size is normal as the state as not converged yet Q_ASSERT((!bve) || ( bve->geometryTracker()->state() == StateTracker::Geometry::State::VALID || bve->geometryTracker()->state() == StateTracker::Geometry::State::SIZE) ); // Resize the contend height, it has to be done after the geometry has been // updated. if (bve && q_ptr->s_ptr->m_pGeoAdapter->capabilities() & GeometryAdapter::Capabilities::TRACKS_QQUICKITEM_GEOMETRY) { const auto geo = bve->decoratedGeometry(); v->contentItem()->setHeight(std::max( geo.y()+geo.height(), v->height() )); emit v->contentHeightChanged( v->contentItem()->height() ); } + + if (oldTve != tve || oldBve != bve) + emit q_ptr->cornerChanged(); } void ViewportSync::geometryUpdated(IndexMetadata *item) { if (m_pReflector->modelTracker()->state() == StateTracker::Model::State::RESETING) return; //TODO it needs another state machine to get rid of the `if` //TODO assert if the size hints don't match reality // This will recompute the geometry item->decoratedGeometry(); if (m_pGeoAdapter->capabilities() & GeometryAdapter::Capabilities::TRACKS_QQUICKITEM_GEOMETRY) q_ptr->d_ptr->updateAvailableEdges(); } void ViewportSync::updateGeometry(IndexMetadata* item) { if (m_pGeoAdapter->capabilities() & GeometryAdapter::Capabilities::TRACKS_QQUICKITEM_GEOMETRY) item->sizeHint(); if (auto i = item->down()) notifyInsert(i); q_ptr->d_ptr->updateAvailableEdges(); refreshVisible(); //notifyInsert(item->down()); } // When the QModelIndex role change void ViewportSync::notifyChange(IndexMetadata* item) { item << IndexMetadata::GeometryAction::MODIFY; } void ViewportSync::notifyRemoval(IndexMetadata* item) { Q_UNUSED(item) if (m_pReflector->modelTracker()->state() == StateTracker::Model::State::RESETING) return; //TODO it needs another state machine to get rid of the `if` // auto tve = m_pReflector->getEdge( // IndexMetadata::EdgeType::VISIBLE, Qt::TopEdge // ); // auto bve = q_ptr->d_ptr->m_pReflector->getEdge( // IndexMetadata::EdgeType::VISIBLE, Qt::BottomEdge // ); // Q_ASSERT(item != bve); //TODO // //FIXME this is horrible // while(auto next = item->down()) { //FIXME dead code // if (next->m_State.state() != StateTracker::Geometry::State::VALID || // next->m_State.state() != StateTracker::Geometry::State::POSITION) // break; // // next->m_State.performAction( // GeometryAction::MOVE, nullptr, nullptr // ); // // Q_ASSERT(next != bve); //TODO // // Q_ASSERT(next->m_State.state() != StateTracker::Geometry::State::VALID); // } // // refreshVisible(); // // q_ptr->d_ptr->updateAvailableEdges(); } //TODO temporary hack to avoid having to implement a complex way to move the // limited number of loaded elements. Given once trimming is enabled, the // number of visible items should be fairely low/constant, it is a good enough // temporary solution. void ViewportSync::refreshVisible() { if (m_pReflector->modelTracker()->state() == StateTracker::Model::State::RESETING) return; //TODO it needs another state machine to get rid of the `if` //TODO eventually move to a relative origin so moving an item to the top // doesn't need to move everything IndexMetadata *item = m_pReflector->getEdge( IndexMetadata::EdgeType::VISIBLE, Qt::TopEdge ); if (!item) return; Q_ASSERT((!item->up()) || !item->up()->isVisible()); auto bve = m_pReflector->getEdge( IndexMetadata::EdgeType::VISIBLE, Qt::BottomEdge ); auto prev = item->up(); // First, make sure the previous elem has a valid size. If, for example, // rowsMoved moves a previously unloaded item to the front, this information // will be lost. if (prev && !prev->isValid()) { // This is the slow path, it can be /very/ slow. Possible mitigation // include adding more lower level methods to never lose track of the // list state, but this makes everything more (too) complex. Another // better solution is more aggressive unloading to the list becomes // smaller. if (!prev->isTopItem()) { qDebug() << "Slow path"; //FIXME this is slow auto i = prev; while((i = i->up()) && i && !i->isValid()); Q_ASSERT(i); while ((i = i->down()) != item) i->decoratedGeometry(); } else prev->setPosition({0.0, 0.0}); } const bool hasSingleItem = item == bve; do { item->sizeHint(); Q_ASSERT(item->geometryTracker()->state() != StateTracker::Geometry::State::INIT); Q_ASSERT(item->geometryTracker()->state() != StateTracker::Geometry::State::POSITION); if (prev) { //FIXME this isn't ok, it needs to take into account the point size //it could by multiple pixels item->setPosition(prev->decoratedGeometry().bottomLeft()); } item->sizeHint(); Q_ASSERT(item->isVisible()); Q_ASSERT(item->isValid()); //FIXME As of 0.1, this still fails from time to time //Q_ASSERT(item->isInSync());//TODO THIS_COMMIT // This `performAction` exists to recover from runtime failures if (!item->isInSync()) item << IndexMetadata::LoadAction::MOVE; } while((!hasSingleItem) && item->up() != bve && (item = item->down())); } void ViewportSync::notifyInsert(IndexMetadata* item) { using GeoState = StateTracker::Geometry::State; Q_ASSERT(item); if (m_pReflector->modelTracker()->state() == StateTracker::Model::State::RESETING) return; //TODO it needs another state machine to get rid of the `if` if (!item) return; const bool needsPosition = item->geometryTracker()->state() == GeoState::INIT || item->geometryTracker()->state() == GeoState::SIZE; // If the item is new and is inserted near valid items, skip some back and // forth and set the position now. if (needsPosition && item->up() && item->up()->geometryTracker()->state() == GeoState::VALID) { item->setPosition(item->up()->decoratedGeometry().bottomLeft()); } // If the item is inserted in front, set the position if (item->isTopItem()) { item->setPosition({0.0, 0.0}); } auto bve = m_pReflector->getEdge( IndexMetadata::EdgeType::VISIBLE, Qt::BottomEdge ); //FIXME this is also horrible do { if (item == bve) { item << IndexMetadata::GeometryAction::MOVE; break; } if (!item->isValid() && item->geometryTracker()->state() != GeoState::POSITION) { break; } item << IndexMetadata::GeometryAction::MOVE; } while((item = item->down())); refreshVisible(); q_ptr->d_ptr->updateAvailableEdges(); } void Viewport::resize(const QRectF& rect) { if (s_ptr->m_pReflector->modelTracker()->state() == StateTracker::Model::State::RESETING) return; //TODO it needs another state machine to get rid of the `if` const bool wasValid = d_ptr->m_ViewRect.size().isValid(); Q_ASSERT(rect.x() == 0); // The {x, y} may not be at {0, 0}, but given it is a relative viewport, // then the content doesn't care about where it is on the screen. d_ptr->m_ViewRect = rect; Q_ASSERT(rect.y() == 0); // For now ignore the case where the content is smaller, it doesn't change // anything. This could eventually change d_ptr->m_UsedRect.setSize(rect.size()); //FIXME remove, wrong s_ptr->refreshVisible(); d_ptr->updateAvailableEdges(); if ((!d_ptr->m_pModelAdapter) || (!d_ptr->m_pModelAdapter->rawModel())) return; if (!d_ptr->m_pModelAdapter->delegate()) return; if (!rect.isValid()) return; // Refresh the content if (!wasValid) s_ptr->m_pReflector->modelTracker() << StateTracker::Model::Action::POPULATE << StateTracker::Model::Action::ENABLE; else { //FIXME make sure the state machine handle the lack of delegate properly s_ptr->m_pReflector->modelTracker() << StateTracker::Model::Action::MOVE; } } IndexMetadata *ViewportSync::metadataForIndex(const QModelIndex& idx) const { return m_pReflector->metadataForIndex(idx); } QQmlEngine *ViewportSync::engine() { if (!m_pEngine) m_pEngine = q_ptr->modelAdapter()->view()->rootContext()->engine(); return m_pEngine; } QQmlComponent *ViewportSync::component() { engine(); m_pComponent = new QQmlComponent(m_pEngine); m_pComponent->setData("import QtQuick 2.4; Item {property QtObject content: null;}", {}); return m_pComponent; } GeometryAdapter *Viewport::geometryAdapter() const { return s_ptr->m_pGeoAdapter; } void Viewport::setGeometryAdapter(GeometryAdapter *a) { s_ptr->m_pGeoAdapter->setCurrentAdapter(a); } +QModelIndex Viewport::indexAt(const QPoint &point) const +{ + return {}; //TODO +} + +QModelIndex Viewport::indexAt(Qt::Corner corner) const +{ + // multi column isn't supported yet, so it is close enough for now + switch (corner) { + case Qt::TopLeftCorner: + case Qt::TopRightCorner: + return indexAt(Qt::TopEdge); + case Qt::BottomLeftCorner: + case Qt::BottomRightCorner: + return indexAt(Qt::BottomEdge); + } + + return {}; +} + +QModelIndex Viewport::indexAt(Qt::Edge edge) const +{ + if (auto tve = s_ptr->m_pReflector->getEdge(IndexMetadata::EdgeType::VISIBLE, edge)) + return tve->index(); + + return {}; +} + #include diff --git a/src/viewport.h b/src/viewport.h index e0c99d9..cf6bd4f 100644 --- a/src/viewport.h +++ b/src/viewport.h @@ -1,82 +1,87 @@ /*************************************************************************** * Copyright (C) 2018 by Emmanuel Lepage Vallee * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ #ifndef KQUICKITEMVIEWS_VIEWPORT_H #define KQUICKITEMVIEWS_VIEWPORT_H // Qt #include #include // KQuickItemViews #include class ViewportPrivate; class ViewportSync; class AbstractItemAdapter; class GeometryAdapter; /** * This class exposes a way to track and iterate a subset of the model. * * It prevents all of the model reflection to have to be loaded in memory * and offers a simpler API to access the loaded sections. * * This class is for internal use and should not be used by views. Please use * `ViewBase` for all relevant use cases. */ class Q_DECL_EXPORT Viewport : public QObject { friend class AbstractItemAdapter; // for the getters defined in viewport.cpp friend class ViewportSync; // its own internal API Q_OBJECT public: explicit Viewport(ModelAdapter* ma); virtual ~Viewport(); /** * Get the current (cartesian) rectangle represented by this range. */ QRectF currentRect() const; ModelAdapter *modelAdapter() const; QSizeF size() const; QPointF position() const; QSizeF totalSize() const; Qt::Edges availableEdges() const; GeometryAdapter *geometryAdapter() const; void setGeometryAdapter(GeometryAdapter *a); void setItemFactory(ViewBase::ItemFactoryBase *factory); void resize(const QRectF& rect); + QModelIndex indexAt(const QPoint & point) const; + QModelIndex indexAt(Qt::Corner corner) const; + QModelIndex indexAt(Qt::Edge edge) const; + ViewportSync *s_ptr; Q_SIGNALS: void contentChanged(); + void cornerChanged(); public: ViewportPrivate *d_ptr; }; #endif