diff --git a/src/adapters/abstractitemadapter.cpp b/src/adapters/abstractitemadapter.cpp index c1a55fa..e433160 100644 --- a/src/adapters/abstractitemadapter.cpp +++ b/src/adapters/abstractitemadapter.cpp @@ -1,659 +1,659 @@ /*************************************************************************** * 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 "abstractitemadapter.h" // libstdc++ #include // Qt #include #include #include #include // KQuickItemViews #include "private/statetracker/viewitem_p.h" #include "adapters/selectionadapter.h" #include "adapters/geometryadapter.h" #include "private/selectionadapter_p.h" #include "private/geostrategyselector_p.h" #include "viewport.h" #include "private/indexmetadata_p.h" #include "private/viewport_p.h" #include "modeladapter.h" #include "private/statetracker/index_p.h" #include "viewbase.h" #include "contextadapterfactory.h" #include "contextadapter.h" class AbstractItemAdapterPrivate : public QObject { Q_OBJECT public: typedef bool(AbstractItemAdapterPrivate::*StateF)(); // Helpers inline void load(); // Actions bool attach (); bool refresh(); bool move (); bool flush (); bool remove (); bool nothing(); bool error (); bool destroy(); bool detach (); bool hide (); static const StateTracker::ViewItem::State m_fStateMap [7][7]; static const StateF m_fStateMachine[7][7]; mutable QSharedPointer m_pLocker; mutable QQuickItem *m_pItem {nullptr}; mutable QQmlContext *m_pContext {nullptr}; // Helpers QPair loadDelegate(QQuickItem* parentI) const; // Attributes AbstractItemAdapter* q_ptr; public Q_SLOTS: void slotDestroyed(); }; /* * The visual elements state changes. * * Note that the ::FAILED elements will always try to self-heal themselves and * go back into FAILED once the self-healing itself failed. */ #define S StateTracker::ViewItem::State:: const StateTracker::ViewItem::State AbstractItemAdapterPrivate::m_fStateMap[7][7] = { /* ATTACH ENTER_BUFFER ENTER_VIEW UPDATE MOVE LEAVE_BUFFER DETACH */ /*POOLING */ { S POOLING, S BUFFER, S ERROR , S ERROR , S ERROR , S ERROR , S POOLED }, /*POOLED */ { S POOLED , S BUFFER, S ACTIVE, S ERROR , S ERROR , S ERROR , S DANGLING }, /*BUFFER */ { S ERROR , S ERROR , S ACTIVE, S BUFFER, S ERROR , S POOLING, S DANGLING }, /*ACTIVE */ { S ERROR , S BUFFER, S ERROR , S ACTIVE, S ACTIVE, S BUFFER , S POOLING }, /*FAILED */ { S ERROR , S BUFFER, S ACTIVE, S ACTIVE, S ACTIVE, S POOLED , S DANGLING }, /*DANGLING*/ { S ERROR , S ERROR , S ERROR , S ERROR , S ERROR , S ERROR , S DANGLING }, /*ERROR */ { S ERROR , S ERROR , S ERROR , S ERROR , S ERROR , S ERROR , S DANGLING }, }; #undef S #define A &AbstractItemAdapterPrivate:: const AbstractItemAdapterPrivate::StateF AbstractItemAdapterPrivate::m_fStateMachine[7][7] = { /* ATTACH ENTER_BUFFER ENTER_VIEW UPDATE MOVE LEAVE_BUFFER DETACH */ /*POOLING */ { A error , A error , A error , A error , A error , A error , A nothing }, /*POOLED */ { A nothing, A attach , A move , A error , A error , A error , A destroy }, /*BUFFER */ { A error , A error , A move , A refresh, A error , A detach , A destroy }, /*ACTIVE */ { A error , A nothing, A nothing, A refresh, A move , A hide , A detach }, /*FAILED */ { A error , A nothing, A nothing, A nothing, A nothing, A nothing, A destroy }, /*DANGLING*/ { A error , A error , A error , A error , A error , A error , A destroy }, /*error */ { A error , A error , A error , A error , A error , A error , A destroy }, }; #undef A AbstractItemAdapter::AbstractItemAdapter(Viewport* r) : s_ptr(new StateTracker::ViewItem(r)), d_ptr(new AbstractItemAdapterPrivate) { d_ptr->q_ptr = this; s_ptr->d_ptr = this; } AbstractItemAdapter::~AbstractItemAdapter() { if (d_ptr->m_pItem) delete d_ptr->m_pItem; if (d_ptr->m_pContext) delete d_ptr->m_pContext; delete d_ptr; } ViewBase* AbstractItemAdapter::view() const { return s_ptr->m_pViewport->modelAdapter()->view(); } void AbstractItemAdapter::resetPosition() { // } int AbstractItemAdapter::row() const { return s_ptr->row(); } int AbstractItemAdapter::column() const { return s_ptr->column(); } QPersistentModelIndex AbstractItemAdapter::index() const { return s_ptr->index(); } AbstractItemAdapter *AbstractItemAdapter::next(Qt::Edge e) const { const auto i = s_ptr->next(e); return i ? i->d_ptr : nullptr; } AbstractItemAdapter* AbstractItemAdapter::parent() const { return nullptr; } void AbstractItemAdapter::updateGeometry() { s_ptr->updateGeometry(); } void StateTracker::ViewItem::setSelected(bool v) { d_ptr->setSelected(v); } QRectF StateTracker::ViewItem::currentGeometry() const { return d_ptr->geometry(); } QQuickItem* StateTracker::ViewItem::item() const { - return d_ptr->item(); + return d_ptr->container(); } bool AbstractItemAdapterPrivate::attach() { if (m_pItem) m_pItem->setVisible(true); return q_ptr->attach(); } bool AbstractItemAdapterPrivate::refresh() { return q_ptr->refresh(); } bool AbstractItemAdapterPrivate::move() { const bool ret = q_ptr->move(); // Views should apply the geometry they have been told to apply. Otherwise // all optimizations brakes. // Q_ASSERT((!ret) || q_ptr->s_ptr->m_pMetadata->isInSync()); return ret; } bool AbstractItemAdapterPrivate::flush() { return q_ptr->flush(); } bool AbstractItemAdapterPrivate::remove() { bool ret = q_ptr->remove(); m_pItem->setParentItem(nullptr); q_ptr->s_ptr->m_pMetadata = nullptr; return ret; } bool AbstractItemAdapterPrivate::hide() { Q_ASSERT(m_pItem); if (!m_pItem) return false; m_pItem->setVisible(false); return true; } // This methodwrap the removal of the element from the view bool AbstractItemAdapterPrivate::detach() { m_pItem->setParentItem(nullptr); remove(); //FIXME q_ptr->s_ptr->m_pMetadata << IndexMetadata::ViewAction::DETACH; Q_ASSERT(q_ptr->s_ptr->state() == StateTracker::ViewItem::State::POOLED); return true; } bool AbstractItemAdapterPrivate::nothing() { return true; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn" bool AbstractItemAdapterPrivate::error() { Q_ASSERT(false); return true; } #pragma GCC diagnostic pop bool AbstractItemAdapterPrivate::destroy() { auto ptrCopy = m_pLocker; //FIXME manage to add to the pool without a SEGFAULT if (m_pItem) { m_pItem->setParentItem(nullptr); delete m_pItem; } m_pItem = nullptr; if (ptrCopy) { QTimer::singleShot(0,[this, ptrCopy]() { if (!ptrCopy) // else the reference will be dropped and the destructor called delete q_ptr; }); if (m_pLocker) m_pLocker.clear(); } else { delete q_ptr; } return true; } bool StateTracker::ViewItem::performAction(IndexMetadata::ViewAction a) { const int s = (int)m_State; m_State = d_ptr->d_ptr->m_fStateMap [s][(int)a]; Q_ASSERT(m_State != StateTracker::ViewItem::State::ERROR); return (d_ptr->d_ptr ->* d_ptr->d_ptr->m_fStateMachine[s][(int)a])(); } int StateTracker::ViewItem::depth() const { return m_pMetadata->indexTracker()->depth(); } QPair, AbstractItemAdapter*> AbstractItemAdapter::weakReference() const { if (d_ptr->m_pLocker) return {d_ptr->m_pLocker, const_cast(this)}; d_ptr->m_pLocker = QSharedPointer(new SelectionLocker()); return {d_ptr->m_pLocker, const_cast(this)}; } void AbstractItemAdapterPrivate::load() { if (m_pContext || m_pItem) return; // Usually if we get here something already failed if (!q_ptr->s_ptr->m_pViewport->modelAdapter()->delegate()) { Q_ASSERT(false); qDebug() << "Cannot attach, there is no delegate"; return; } auto pair = loadDelegate(q_ptr->view()->contentItem()); if (!pair.first) { qDebug() << "Item failed to load" << q_ptr->index().data(); return; } if (!pair.first->z()) pair.first->setZ(1); /*d()->m_DepthChart[depth()] = std::max( d()->m_DepthChart[depth()], pair.first->height() );*/ m_pContext = pair.second; m_pItem = pair.first; // QtQuick can decide to destroy it even with C++ ownership, so be it connect(m_pItem, &QObject::destroyed, this, &AbstractItemAdapterPrivate::slotDestroyed); Q_ASSERT(q_ptr->s_ptr->m_pMetadata); q_ptr->s_ptr->m_pMetadata->contextAdapter()->context(); Q_ASSERT(q_ptr->s_ptr->m_pMetadata->contextAdapter()->context() == m_pContext); if (q_ptr->s_ptr->m_pViewport->s_ptr->m_pGeoAdapter->capabilities() & GeometryAdapter::Capabilities::HAS_AHEAD_OF_TIME) { q_ptr->s_ptr->m_pMetadata->performAction( IndexMetadata::GeometryAction::MODIFY ); } else { //TODO it should still call the GeometryAdapter, but most of them are // currently too buggy for that to help. q_ptr->s_ptr->m_pMetadata->setSize( QSizeF(m_pItem->width(), m_pItem->height()) ); } Q_ASSERT(q_ptr->s_ptr->m_pMetadata->geometryTracker()->state() != StateTracker::Geometry::State::INIT); } QQmlContext *AbstractItemAdapter::context() const { d_ptr->load(); return d_ptr->m_pContext; } -QQuickItem *AbstractItemAdapter::item() const +QQuickItem *AbstractItemAdapter::container() const { d_ptr->load(); return d_ptr->m_pItem; } Viewport *AbstractItemAdapter::viewport() const { return s_ptr->m_pViewport; } QRectF AbstractItemAdapter::geometry() const { if (!d_ptr->m_pItem) return {}; - const QPointF p = item()->mapFromItem(view()->contentItem(), {0,0}); + const QPointF p = container()->mapFromItem(view()->contentItem(), {0,0}); return { -p.x(), -p.y(), - item()->width(), - item()->height() + container()->width(), + container()->height() }; } QRectF AbstractItemAdapter::decoratedGeometry() const { return s_ptr->m_pMetadata->decoratedGeometry(); } qreal AbstractItemAdapter::borderDecoration(Qt::Edge e) const { return s_ptr->m_pMetadata->borderDecoration(e); } void AbstractItemAdapter::setBorderDecoration(Qt::Edge e, qreal r) { s_ptr->m_pMetadata->setBorderDecoration(e, r); } bool AbstractItemAdapter::refresh() { return true; } bool AbstractItemAdapter::remove() { return true; } bool AbstractItemAdapter::move() { const auto a = s_ptr->m_pViewport->geometryAdapter(); Q_ASSERT(a); const auto pos = a->positionHint(index(), this); - item()->setSize(a->sizeHint(index(), this)); - item()->setX(pos.x()); - item()->setY(pos.y()); + container()->setSize(a->sizeHint(index(), this)); + container()->setX(pos.x()); + container()->setY(pos.y()); return true; } bool AbstractItemAdapter::attach() { Q_ASSERT(index().isValid()); - return item();// && move(); + return container();// && move(); } bool AbstractItemAdapter::flush() { return true; } void AbstractItemAdapter::setSelected(bool /*s*/) {} QPair AbstractItemAdapterPrivate::loadDelegate(QQuickItem* parentI) const { const auto delegate = q_ptr->s_ptr->m_pViewport->modelAdapter()->delegate(); if (!delegate) { qWarning() << "No delegate is set"; return {}; } auto pctx = q_ptr->s_ptr->m_pMetadata->contextAdapter()->context(); // Create a parent item to hold the delegate and all children auto container = qobject_cast(q_ptr->s_ptr->m_pViewport->s_ptr->component()->create(pctx)); container->setWidth(q_ptr->view()->width()); q_ptr->s_ptr->m_pViewport->s_ptr->engine()->setObjectOwnership(container, QQmlEngine::CppOwnership); container->setParentItem(parentI); // Create a context with all the tree roles auto ctx = new QQmlContext(pctx); // Create the delegate auto item = qobject_cast(delegate->create(ctx)); q_ptr->s_ptr->m_pViewport->s_ptr->engine()->setObjectOwnership(item, QQmlEngine::CppOwnership); // It allows the children to be added anyway if(!item) { if (!delegate->errorString().isEmpty()) qWarning() << delegate->errorString(); return {container, pctx}; } item->setWidth(q_ptr->view()->width()); item->setParentItem(container); // Resize the container container->setHeight(item->height()); // Make sure it can be resized dynamically QObject::connect(item, &QQuickItem::heightChanged, container, [container, item](){ container->setHeight(item->height()); }); container->setProperty("content", QVariant::fromValue(item)); return {container, pctx}; } void StateTracker::ViewItem::updateGeometry() { const auto sm = m_pViewport->modelAdapter()->selectionAdapter(); if (sm && sm->selectionModel() && sm->selectionModel()->currentIndex() == index()) sm->s_ptr->updateSelection(index()); m_pViewport->s_ptr->geometryUpdated(m_pMetadata); } QQmlContext *StateTracker::ViewItem::context() const { return d_ptr->d_ptr->m_pContext; } void AbstractItemAdapter::setCollapsed(bool v) { s_ptr->setCollapsed(v); } bool AbstractItemAdapter::isCollapsed() const { return s_ptr->isCollapsed(); } QSizeF AbstractItemAdapter::sizeHint() const { return s_ptr->m_pMetadata->sizeHint(); } QPersistentModelIndex StateTracker::ViewItem::index() const { return QModelIndex(m_pMetadata->indexTracker()->index()); } /** * Flatten the tree as a linked list. * * Returns the previous non-failed item. */ StateTracker::ViewItem* StateTracker::ViewItem::up() const { Q_ASSERT(m_State == State::ACTIVE || m_State == State::BUFFER || m_State == State::FAILED || m_State == State::POOLING || m_State == State::DANGLING //FIXME add a new state for // "deletion in progress" or swap the f call and set state ); auto ret = m_pMetadata->indexTracker()->up(); //TODO support collapsed nodes // Linearly look for a valid element. Doing this here allows the views // that implement this (abstract) class to work without having to always // check if some of their item failed to load. This is non-fatal in the // other Qt views, so it isn't fatal here either. while (ret && !ret->metadata()->viewTracker()) ret = ret->up(); if (ret && ret->metadata()->viewTracker()) Q_ASSERT(ret->metadata()->viewTracker()->m_State != State::POOLING && ret->metadata()->viewTracker()->m_State != State::DANGLING); auto vi = ret ? ret->metadata()->viewTracker() : nullptr; // Do not allow navigating past the loaded edges if (vi && (vi->m_State == State::POOLING || vi->m_State == State::POOLED)) return nullptr; return vi; } /** * Flatten the tree as a linked list. * * Returns the next non-failed item. */ StateTracker::ViewItem* StateTracker::ViewItem::down() const { Q_ASSERT(m_State == State::ACTIVE || m_State == State::BUFFER || m_State == State::FAILED || m_State == State::POOLING || m_State == State::DANGLING //FIXME add a new state for // "deletion in progress" or swap the f call and set state ); auto ret = m_pMetadata->indexTracker(); //TODO support collapsed entries // Recursively look for a valid element. Doing this here allows the views // that implement this (abstract) class to work without having to always // check if some of their item failed to load. This is non-fatal in the // other Qt views, so it isn't fatal here either. while (ret && !ret->metadata()->viewTracker()) ret = ret->down(); auto vi = ret ? ret->metadata()->viewTracker() : nullptr; // Do not allow navigating past the loaded edges if (vi && (vi->m_State == State::POOLING || vi->m_State == State::POOLED)) return nullptr; return vi; } StateTracker::ViewItem *StateTracker::ViewItem::next(Qt::Edge e) const { switch (e) { case Qt::TopEdge: return up(); case Qt::BottomEdge: return down(); case Qt::LeftEdge: return left(); case Qt::RightEdge: return right(); } Q_ASSERT(false); return {}; } int StateTracker::ViewItem::row() const { return m_pMetadata->indexTracker()->effectiveRow(); } int StateTracker::ViewItem::column() const { return m_pMetadata->indexTracker()->effectiveColumn(); } void StateTracker::ViewItem::setCollapsed(bool v) { m_pMetadata->setCollapsed(v); } bool StateTracker::ViewItem::isCollapsed() const { return m_pMetadata->isCollapsed(); } StateTracker::ViewItem::State StateTracker::ViewItem::state() const { return m_State; } void AbstractItemAdapterPrivate::slotDestroyed() { m_pItem = nullptr; } #include diff --git a/src/adapters/abstractitemadapter.h b/src/adapters/abstractitemadapter.h index 5b17657..dfb458f 100644 --- a/src/adapters/abstractitemadapter.h +++ b/src/adapters/abstractitemadapter.h @@ -1,269 +1,269 @@ /*************************************************************************** * 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 KQUICKITEMVIEWS_ABSTRACTITEMADAPTER_H #define KQUICKITEMVIEWS_ABSTRACTITEMADAPTER_H #include #include class QQuickItem; class QQmlContext; class AbstractItemAdapterPrivate; namespace StateTracker { class ViewItem; class ModelItem; } class ViewBase; class Viewport; class ContextExtension; /** * This class must be extended by the views to bind QModelIndex with a GUI element. * * This is the class implementation should use to navigate the model indices. It * provides a simple geometric linked list between indices and provide built-in * memory management, including recycling. * * This is optionally a template class to avoid having to perform static casts * when implementing. * * All moderately cartesian views such as list, table, trees and most charts * should use this class along with `ViewBase`. Graphs and advanced * visualizations should probably use a lower level API. * * These objects are created if and only if those 2 conditions are met: * * * The QModelIndex is currently part of a tracked range (see Viewport). * * It didn't fail to load. * * If the overloaded functions returns false, then the consumer of this * class should no longer use the object as it may have been deleted or * recycled. * * Note that this should **NEVER** be stored and an instance may be reused for * other indices or deleted without prior notice. The memory management of this * class is designed for high performance, not ease of use. If you are * implementing views in C++, then this is probably what you want, so think * twice before cursing at how low level this is. * * Note that this object usage should be strictly limited to its overloaded * functions and external usage (from the view class or elsewhere) is * strongly discouraged. If you think the provided interface isn't enough, * report a bug rather than work around it to avoid crashes. */ class AbstractItemAdapter { friend struct StateTracker::ModelItem; //state tracking friend class StateTracker::ViewItem; //its internally shared properties friend class AbstractItemAdapterPrivate; // like (Q_DECLARE_PRIVATE) public: explicit AbstractItemAdapter(Viewport* r); virtual ~AbstractItemAdapter(); ViewBase* view() const; /** * The QML context for the `item`. * * This is lazy loaded. */ virtual QQmlContext *context() const; /** * The QQuickItem used for this item. * * By default, it will create a container in which another item will * be placed. This isn't as optimial as placing the item directly, but * allows for a lot of boilerplate code to be handled internally. */ - virtual QQuickItem *item() const; + virtual QQuickItem *container() const; Viewport *viewport() const; /** * The external state if the item. * * This is different from the internal state and only has a subset the * consumer (views) care about. Note that internally, it can be part of a * recycling pool or other types of caches. */ enum class State { BUFFER , /*!< Is close enough to the visible area to have been loaded */ VISIBLE, /*!< Is currently displayed */ INVALID, /*!< You should not use this instance */ }; /** * Get the nearby element on the edge of this item. * * Note that this applies a Cartesian projection on the whole model, so * a nearby element can be a parent, children or sibling. Also note that * elements are lazy loaded and recycled, so there is no guarantee that * the element will exits and it should never be stored. */ AbstractItemAdapter *next(Qt::Edge e) const; /** * This method return the item representing the QModelIndex parent. * * It will return it **ONLY IF THE PARENT IS PART OF THE VISIBLE RANGE**. */ AbstractItemAdapter* parent() const; /** * The item row. * * Note that this doesn't always match QModelIndex::row() because this * value is updated when the `rowsAboutToBeModed` signal is sent rather * than after the change takes effect. */ int row() const; /** * The item column. * * Note that this doesn't always match QModelIndex::column() because this * value is updated when the `columnsAboutToBeModed` signal is sent rather * than after the change takes effect. */ int column() const; /** * The model index. * * Please do not use .row() and .column() on the index and rather use * ::row() and ::column() provided by this class. Otherwise items being * moved wont be rendered correctly. */ QPersistentModelIndex index() const; // Actions /** * Force the position to be computed again. * * The item position is managed by the implementation. However external * events such as resizing the window or switching from mobile to desktop * mode can require the position to be reconsidered. * * This will call `AbstractItemAdapter::move` and all the actions such * as moving the children items, updating the visible range and general * housekeeping tasks so the view implementation does not have to care * about them. */ void resetPosition(); struct SelectionLocker {}; /** * Get a pointer for this item. * * The SelectionLocker (first element of the pair) will be nullptr if the * item no longer belong to the same QModelIndex as when it was acquired. * * Keep in mind that these objects are recycled, so comparing the * AbstractItemAdapter pointer tells nothing. * * **DO NOT STORE IT AS A QSharedPointer**. This is only intended to be * stored as a QWeakPointer. It will */ QPair, AbstractItemAdapter*> weakReference() const; /** * The size and position necessary to draw this item. * * The default implementation uses the item() geometry. */ virtual QRectF geometry() const; QRectF decoratedGeometry() const; /** * In many case, it is useful or necessary to place additional components * or just empty space around a delegate instance. * * This can be, for example, the treeview vertical indentation/expand * indicator or the listview category delegate. * * Note that the top and bottom edge decoration include the width of the * left and right ones. */ qreal borderDecoration(Qt::Edge e) const; void setBorderDecoration(Qt::Edge e, qreal r); /** * Set if the children of this item should be skipped from the view. */ void setCollapsed(bool v); bool isCollapsed() const; /** * Implement this function when selecting an item require extra operations * to be executed. * * The default implementation does nothing. */ virtual void setSelected(bool s); void updateGeometry(); virtual QSizeF sizeHint() const; void dismissCacheEntry(ContextExtension* e, int id); // Shared private data StateTracker::ViewItem *s_ptr; protected: /** * This instance is about to be added to the view. * * The default implementation calls move, but some views might need to * perform extra operations only when the item is added to the viewport. */ virtual bool attach (); /** * Update the delegate instance when the model index changes. * * The default implementation refreshes the role values to the context. */ virtual bool refresh(); /** * Move the delegate instance when it moves itself or its siblings change. */ virtual bool move (); /** * This instance is about to be recycled, if it holds a state, remove it. * * The default implementation does nothing. */ virtual bool flush (); /** * This instance is going to be removed from the view. */ virtual bool remove (); private: AbstractItemAdapterPrivate* d_ptr; }; #endif diff --git a/src/views/hierarchyview.cpp b/src/views/hierarchyview.cpp index 8d64951..fabb035 100644 --- a/src/views/hierarchyview.cpp +++ b/src/views/hierarchyview.cpp @@ -1,160 +1,160 @@ /*************************************************************************** * 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 "hierarchyview.h" // KQuickItemViews #include "adapters/abstractitemadapter.h" // Qt #include /** * Polymorphic tree item for the SingleModelViewBase. * * Classes implementing SingleModelViewBase need to provide an implementation of the pure * virtual functions. It is useful, for example, to manage both a raster and * QQuickItem based version of a view. * * The state is managed by the SingleModelViewBase and it's own protected virtual methods. */ class HierarchyViewItem final : public AbstractItemAdapter { public: explicit HierarchyViewItem(Viewport* r); virtual ~HierarchyViewItem(); // Actions virtual bool move () override; virtual bool remove () override; private: bool m_IsHead { false }; }; class HierarchyViewPrivate { public: // When all elements are assumed to have the same height, life is easy QVector m_DepthChart {0}; HierarchyView* q_ptr; }; HierarchyView::HierarchyView(QQuickItem* parent) : SingleModelViewBase(new ItemFactory(), parent), d_ptr(new HierarchyViewPrivate) { d_ptr->q_ptr = this; } HierarchyView::~HierarchyView() { delete d_ptr; } HierarchyViewItem::HierarchyViewItem(Viewport* r) : AbstractItemAdapter(r) { } HierarchyViewItem::~HierarchyViewItem() { - delete item(); + delete container(); } bool HierarchyViewItem::move() { // Will happen when trying to move a FAILED, but buffered item - if (!item()) { + if (!container()) { qDebug() << "NO ITEM" << index().data(); return false; } - item()->setWidth(view()->contentItem()->width()); + container()->setWidth(view()->contentItem()->width()); auto nextElem = static_cast(next(Qt::BottomEdge)); auto prevElem = static_cast(next(Qt::TopEdge)); // The root has been moved in the middle of the tree, find the new root //TODO maybe add a deterministic API instead of O(N) lookup if (prevElem && m_IsHead) { m_IsHead = false; auto root = prevElem; while (auto prev = root->next(Qt::TopEdge)) root = static_cast(prev); root->move(); Q_ASSERT(root->m_IsHead); } // So other items can be GCed without always resetting to 0x0, note that it // might be a good idea to extend Flickable to support a virtual // origin point. if ((!prevElem) || (nextElem && nextElem->m_IsHead)) { - auto anchors = qvariant_cast(item()->property("anchors")); + auto anchors = qvariant_cast(container()->property("anchors")); anchors->setProperty("top", {}); - item()->setY(0); + container()->setY(0); m_IsHead = true; } else if (prevElem) { Q_ASSERT(!m_IsHead); - item()->setProperty("y", {}); - auto anchors = qvariant_cast(item()->property("anchors")); - anchors->setProperty("top", prevElem->item()->property("bottom")); + container()->setProperty("y", {}); + auto anchors = qvariant_cast(container()->property("anchors")); + anchors->setProperty("top", prevElem->container()->property("bottom")); } // Now, update the next anchors if (nextElem) { nextElem->m_IsHead = false; - nextElem->item()->setProperty("y", {}); + nextElem->container()->setProperty("y", {}); - auto anchors = qvariant_cast(nextElem->item()->property("anchors")); - anchors->setProperty("top", item()->property("bottom")); + auto anchors = qvariant_cast(nextElem->container()->property("anchors")); + anchors->setProperty("top", container()->property("bottom")); } updateGeometry(); return true; } bool HierarchyViewItem::remove() { - if (item()) { - item()->setParent(nullptr); - item()->setParentItem(nullptr); - item()->setVisible(false); + if (container()) { + container()->setParent(nullptr); + container()->setParentItem(nullptr); + container()->setVisible(false); } auto nextElem = static_cast(next(Qt::BottomEdge)); auto prevElem = static_cast(next(Qt::TopEdge)); if (nextElem) { if (m_IsHead) { - auto anchors = qvariant_cast(nextElem->item()->property("anchors")); + auto anchors = qvariant_cast(nextElem->container()->property("anchors")); anchors->setProperty("top", {}); - item()->setY(0); + container()->setY(0); nextElem->m_IsHead = true; } else { //TODO maybe eventually use a state machine for this - auto anchors = qvariant_cast(nextElem->item()->property("anchors")); - anchors->setProperty("top", prevElem->item()->property("bottom")); + auto anchors = qvariant_cast(nextElem->container()->property("anchors")); + anchors->setProperty("top", prevElem->container()->property("bottom")); } } return true; } diff --git a/src/views/listview.cpp b/src/views/listview.cpp index 8936cea..7d5ba8a 100644 --- a/src/views/listview.cpp +++ b/src/views/listview.cpp @@ -1,662 +1,662 @@ /*************************************************************************** * 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 "listview.h" // KQuickItemViews #include "adapters/abstractitemadapter.h" #include "adapters/contextadapter.h" class ListViewItem; // Qt #include #include #include /** * Holds the metadata associated with a section. * * A section is created when a ListViewItem has a property that differs * from the previous ListViewItem in the list. It can also cross reference * into an external model to provide more flexibility. */ struct ListViewSection final { explicit ListViewSection( ListViewItem* owner, const QVariant& value ); virtual ~ListViewSection(); QQuickItem* m_pItem {nullptr}; QQmlContext* m_pContent {nullptr}; int m_Index { 0 }; int m_RefCount { 1 }; QVariant m_Value { }; ListViewSection* m_pPrevious{nullptr}; ListViewSection* m_pNext {nullptr}; ListViewPrivate* d_ptr {nullptr}; // Mutator QQuickItem* item(QQmlComponent* component); // Helpers void reparentSection(ListViewItem* newParent, ViewBase* view); // Getter ListViewItem *owner() const; // Setter void setOwner(ListViewItem *o); private: ListViewItem *m_pOwner {nullptr}; }; class ListViewItem : public AbstractItemAdapter { public: explicit ListViewItem(Viewport* r); virtual ~ListViewItem(); // Actions virtual bool attach () override; virtual bool move () override; virtual bool remove () override; ListViewSection* m_pSection {nullptr}; // Setters ListViewSection* setSection(ListViewSection* s, const QVariant& val); ListViewPrivate* d() const; }; class ListViewPrivate : public QObject { Q_OBJECT public: explicit ListViewPrivate(ListView* p) : QObject(p), q_ptr(p){} // When all elements are assumed to have the same height, life is easy QVector m_DepthChart { 0 }; ListViewSections* m_pSections {nullptr}; // Sections QQmlComponent* m_pDelegate {nullptr}; QString m_Property { }; QStringList m_Roles { }; int m_CachedRole { 0 }; mutable bool m_IndexLoaded { false }; ListViewSection* m_pFirstSection {nullptr}; QSharedPointer m_pSectionModel; // Helpers ListViewSection* getSection(ListViewItem* i); void reloadSectionIndices() const; ListView* q_ptr; public Q_SLOTS: void slotCurrentIndexChanged(const QModelIndex& index); }; ListView::ListView(QQuickItem* parent) : SingleModelViewBase(new ItemFactory(), parent), d_ptr(new ListViewPrivate(this)) { connect(this, &SingleModelViewBase::currentIndexChanged, d_ptr, &ListViewPrivate::slotCurrentIndexChanged); } ListViewItem::~ListViewItem() { // If this item is the section owner, assert before crashing if (m_pSection && m_pSection->owner() == this) { if (m_pSection->m_RefCount == 1) delete m_pSection; else Q_ASSERT(false); //TODO } } ListView::~ListView() { applyModelChanges(nullptr); if (d_ptr->m_pSections) delete d_ptr->m_pSections; d_ptr->m_pSections = nullptr; } int ListView::count() const { return rawModel() ? rawModel()->rowCount() : 0; } int ListView::currentIndex() const { return selectionModel()->currentIndex().row(); } void ListView::setCurrentIndex(int index) { if (!rawModel()) return; SingleModelViewBase::setCurrentIndex( rawModel()->index(index, 0), QItemSelectionModel::ClearAndSelect ); } ListViewSections* ListView::section() const { if (!d_ptr->m_pSections) { d_ptr->m_pSections = new ListViewSections( const_cast(this) ); } return d_ptr->m_pSections; } ListViewSection::ListViewSection(ListViewItem* owner, const QVariant& value) : m_Value(value), d_ptr(owner->d()), m_pOwner(owner) { m_pContent = new QQmlContext(owner->view()->rootContext()); m_pContent->setContextProperty("section", value); owner->setBorderDecoration(Qt::TopEdge, 45.0); } QQuickItem* ListViewSection::item(QQmlComponent* component) { if (m_pItem) return m_pItem; m_pItem = qobject_cast(component->create( m_pContent )); m_pItem->setParentItem(owner()->view()->contentItem()); return m_pItem; } ListViewSection* ListViewItem::setSection(ListViewSection* s, const QVariant& val) { if ((!s) || s->m_Value != val) return nullptr; const auto p = static_cast(next(Qt::TopEdge)); const auto n = static_cast(next(Qt::BottomEdge)); // Garbage collect or change the old section owner if (m_pSection) { if (--m_pSection->m_RefCount <= 0) delete m_pSection; else if (p && n && p->m_pSection && p->m_pSection != m_pSection && n->m_pSection == m_pSection) m_pSection->setOwner(n); else if (p && p->m_pSection != m_pSection) Q_ASSERT(false); // There is a bug somewhere else } m_pSection = s; s->m_RefCount++; if ((!p) || p->m_pSection != s) s->setOwner(this); return s; } static void applyRoles(QQmlContext* ctx, const QModelIndex& self) { auto m = self.model(); if (Q_UNLIKELY(!m)) return; Q_ASSERT(self.model()); const auto hash = self.model()->roleNames(); // Add all roles to the for (auto i = hash.constBegin(); i != hash.constEnd(); ++i) ctx->setContextProperty(i.value() , self.data(i.key())); // Set extra index to improve ListView compatibility ctx->setContextProperty(QStringLiteral("index" ) , self.row() ); ctx->setContextProperty(QStringLiteral("rootIndex" ) , self ); ctx->setContextProperty(QStringLiteral("rowCount" ) , m->rowCount(self) ); } /** * No lookup is performed, it is based on the previous entry and nothing else. * * This view only supports list. If it's with a tree, it will break and "don't * do this". */ ListViewSection* ListViewPrivate::getSection(ListViewItem* i) { if (m_pSections->property().isEmpty() || !m_pDelegate) return nullptr; const auto val = q_ptr->rawModel()->data(i->index(), m_pSections->role()); if (i->m_pSection && i->m_pSection->m_Value == val) return i->m_pSection; const auto prev = static_cast(i->next(Qt::TopEdge)); const auto next = static_cast(i->next(Qt::BottomEdge)); // The section owner isn't currently loaded if ((!prev) && i->row() > 0) { //Q_ASSERT(false); //TODO when GC is enabled, the assert is to make sure I don't forget return nullptr; } // Check if the nearby sections are compatible for (auto& s : { prev ? prev->m_pSection : nullptr, m_pFirstSection, next ? next->m_pSection : nullptr }) if (auto ret = i->setSection(s, val)) return ret; // Create a section i->m_pSection = new ListViewSection(i, val); Q_ASSERT(i->m_pSection->m_RefCount == 1); // Update the double linked list if (prev && prev->m_pSection) { if (prev->m_pSection->m_pNext) { prev->m_pSection->m_pNext->m_pPrevious = i->m_pSection; i->m_pSection->m_pNext = prev->m_pSection->m_pNext; } prev->m_pSection->m_pNext = i->m_pSection; i->m_pSection->m_pPrevious = prev->m_pSection; i->m_pSection->m_Index = prev->m_pSection->m_Index + 1; Q_ASSERT(prev->m_pSection != prev->m_pSection->m_pNext); Q_ASSERT(i->m_pSection->m_pPrevious != i->m_pSection); } m_pFirstSection = m_pFirstSection ? m_pFirstSection : i->m_pSection; Q_ASSERT(m_pFirstSection); if (m_pSectionModel && !m_IndexLoaded) reloadSectionIndices(); if (m_pSectionModel) { const auto idx = m_pSectionModel->index(i->m_pSection->m_Index, 0); Q_ASSERT((!idx.isValid()) || idx.model() == m_pSectionModel); //note: If you wish to fork this class, you can create a second context // manager and avoid the private API. Given it is available, this isn't // done here. applyRoles( i->m_pSection->m_pContent, idx); } // Create the item *after* applyRoles to avoid O(N) number of reloads q_ptr->rootContext()->engine()->setObjectOwnership( i->m_pSection->item(m_pDelegate), QQmlEngine::CppOwnership ); return i->m_pSection; } /** * Set indices for each sections to the right value. */ void ListViewPrivate::reloadSectionIndices() const { int idx = 0; for (auto i = m_pFirstSection; i; i = i->m_pNext) { Q_ASSERT(i != i->m_pNext); i->m_Index = idx++; } //FIXME this assumes all sections are always loaded, this isn't correct m_IndexLoaded = m_pFirstSection != nullptr; } ListViewItem::ListViewItem(Viewport* r) : AbstractItemAdapter(r) { } ListViewPrivate* ListViewItem::d() const { return static_cast(view())->ListView::d_ptr; } bool ListViewItem::attach() { // This will trigger the lazy-loading of the item - if (!item()) + if (!container()) return false; // When the item resizes itself - QObject::connect(item(), &QQuickItem::heightChanged, item(), [this](){ + QObject::connect(container(), &QQuickItem::heightChanged, container(), [this](){ updateGeometry(); }); return move(); } void ListViewSection::setOwner(ListViewItem* newParent) { if (m_pOwner == newParent) return; - if (m_pOwner->item()) { - auto otherAnchors = qvariant_cast(newParent->item()->property("anchors")); - auto anchors = qvariant_cast(m_pOwner->item()->property("anchors")); + if (m_pOwner->container()) { + auto otherAnchors = qvariant_cast(newParent->container()->property("anchors")); + auto anchors = qvariant_cast(m_pOwner->container()->property("anchors")); const auto newPrevious = static_cast(m_pOwner->next(Qt::TopEdge)); Q_ASSERT(newPrevious != m_pOwner); // Prevent a loop while moving - if (otherAnchors && otherAnchors->property("top") == m_pOwner->item()->property("bottom")) { + if (otherAnchors && otherAnchors->property("top") == m_pOwner->container()->property("bottom")) { anchors->setProperty("top", {}); otherAnchors->setProperty("top", {}); otherAnchors->setProperty("y", {}); } - anchors->setProperty("top", newParent->item() ? - newParent->item()->property("bottom") : QVariant() + anchors->setProperty("top", newParent->container() ? + newParent->container()->property("bottom") : QVariant() ); // Set the old owner new anchors - if (newPrevious && newPrevious->item()) - anchors->setProperty("top", newPrevious->item()->property("bottom")); + if (newPrevious && newPrevious->container()) + anchors->setProperty("top", newPrevious->container()->property("bottom")); otherAnchors->setProperty("top", m_pItem->property("bottom")); } else - newParent->item()->setY(0); + newParent->container()->setY(0); // Cleanup the previous owner decoration if (m_pOwner) { m_pOwner->setBorderDecoration(Qt::TopEdge, 0); } m_pOwner = newParent; // Update the owner decoration size m_pOwner->setBorderDecoration(Qt::TopEdge, 45.0); } void ListViewSection::reparentSection(ListViewItem* newParent, ViewBase* view) { if (!m_pItem) return; - if (newParent && newParent->item()) { + if (newParent && newParent->container()) { auto anchors = qvariant_cast(m_pItem->property("anchors")); - anchors->setProperty("top", newParent->item()->property("bottom")); + anchors->setProperty("top", newParent->container()->property("bottom")); m_pItem->setParentItem(owner()->view()->contentItem()); } else { auto anchors = qvariant_cast(m_pItem->property("anchors")); anchors->setProperty("top", {}); m_pItem->setY(0); } if (!m_pItem->width()) m_pItem->setWidth(view->contentItem()->width()); // Update the chain //FIXME crashes /*if (newParent && newParent->m_pSection && newParent->m_pSection->m_pNext != this) { Q_ASSERT(newParent->m_pSection != this); Q_ASSERT(newParent->m_pSection->m_pNext != this); Q_ASSERT(newParent->m_pSection->m_pNext->m_pPrevious != this); m_pNext = newParent->m_pSection->m_pNext; newParent->m_pSection->m_pNext = this; m_pPrevious = newParent->m_pSection; if (m_pNext) { m_pNext->m_pPrevious = this; Q_ASSERT(m_pNext != this); } Q_ASSERT(m_pPrevious != this); } else if (!newParent) { m_pPrevious = nullptr; m_pNext = d_ptr->m_pFirstSection; d_ptr->m_pFirstSection = this; }*/ } bool ListViewItem::move() { - if (!item()) + if (!container()) return false; auto prev = static_cast(next(Qt::TopEdge)); const QQuickItem* prevItem = nullptr; if (d()->m_pSections) if (auto sec = d()->getSection(this)) { // The item is no longer the first in the section if (sec->owner() == this) { ListViewItem* newOwner = nullptr; while (prev && prev->m_pSection == sec) { newOwner = prev; prev = static_cast(prev->next(Qt::TopEdge)); } if (newOwner) { if (newOwner == static_cast(next(Qt::TopEdge))) prev = newOwner; sec->setOwner(newOwner); } sec->reparentSection(prev, view()); if (sec->m_pItem) prevItem = sec->m_pItem; } else if (sec->owner()->row() > row()) { //TODO remove once correctly implemented //HACK to force reparenting when the elements move up sec->setOwner(this); sec->reparentSection(prev, view()); prevItem = sec->m_pItem; } } // Reset the "real" previous element prev = static_cast(next(Qt::TopEdge)); const qreal y = d()->m_DepthChart.first()*row(); - if (item()->width() != view()->contentItem()->width()) - item()->setWidth(view()->contentItem()->width()); + if (container()->width() != view()->contentItem()->width()) + container()->setWidth(view()->contentItem()->width()); - prevItem = prevItem ? prevItem : prev ? prev->item() : nullptr; + prevItem = prevItem ? prevItem : prev ? prev->container() : nullptr; // So other items can be GCed without always resetting to 0x0, note that it // might be a good idea to extend Flickable to support a virtual // origin point. if (!prevItem) - item()->setY(y); + container()->setY(y); else { // Row can be 0 if there is a section - Q_ASSERT(row() || (!prev) || (!prev->item())); + Q_ASSERT(row() || (!prev) || (!prev->container())); // Prevent loops when swapping 2 items auto otherAnchors = qvariant_cast(prevItem->property("anchors")); - auto anchors = qvariant_cast(item()->property("anchors")); + auto anchors = qvariant_cast(container()->property("anchors")); - if (otherAnchors && otherAnchors->property("top") == item()->property("bottom")) { + if (otherAnchors && otherAnchors->property("top") == container()->property("bottom")) { anchors->setProperty("top", {}); otherAnchors->setProperty("top", {}); otherAnchors->setProperty("y", {}); } // Avoid creating too many race conditions if (anchors->property("top") != prevItem->property("bottom")) anchors->setProperty("top", prevItem->property("bottom")); } updateGeometry(); return true; } bool ListViewItem::remove() { if (m_pSection && --m_pSection->m_RefCount <= 0) { delete m_pSection; } else if (m_pSection && m_pSection->owner() == this) { // Reparent the section if (auto n = static_cast(next(Qt::BottomEdge))) { if (n->m_pSection == m_pSection) m_pSection->setOwner(n); /*else Q_ASSERT(false);*/ } /*else Q_ASSERT(false);*/ } m_pSection = nullptr; //TODO move back into treeview2 //TODO check if the item has references, if it does, just release the shared // pointer and move on. return true; } ListViewSections::ListViewSections(ListView* parent) : QObject(parent), d_ptr(parent->d_ptr) { } ListViewSection::~ListViewSection() { if (m_pPrevious) { Q_ASSERT(m_pPrevious != m_pNext); m_pPrevious->m_pNext = m_pNext; } if (m_pNext) m_pNext->m_pPrevious = m_pPrevious; if (this == d_ptr->m_pFirstSection) d_ptr->m_pFirstSection = m_pNext; d_ptr->m_IndexLoaded = false; if (m_pItem) delete m_pItem; if (m_pContent) delete m_pContent; } ListViewSections::~ListViewSections() {} QQmlComponent* ListViewSections::delegate() const { return d_ptr->m_pDelegate; } void ListViewSections::setDelegate(QQmlComponent* component) { d_ptr->m_pDelegate = component; } QString ListViewSections::property() const { return d_ptr->m_Property; } int ListViewSections::role() const { if (d_ptr->m_Property.isEmpty() || !d_ptr->q_ptr->rawModel()) return Qt::DisplayRole; if (d_ptr->m_CachedRole) return d_ptr->m_CachedRole; const auto roles = d_ptr->q_ptr->rawModel()->roleNames(); if (!(d_ptr->m_CachedRole = roles.key(d_ptr->m_Property.toLatin1()))) { qWarning() << d_ptr->m_Property << "is not a model property"; return Qt::DisplayRole; } return d_ptr->m_CachedRole; } void ListViewSections::setProperty(const QString& property) { d_ptr->m_Property = property; } QStringList ListViewSections::roles() const { return d_ptr->m_Roles; } void ListViewSections::setRoles(const QStringList& list) { d_ptr->m_Roles = list; } QSharedPointer ListViewSections::model() const { return d_ptr->m_pSectionModel; } void ListViewSections::setModel(const QSharedPointer& m) { d_ptr->m_pSectionModel = m; } void ListViewPrivate::slotCurrentIndexChanged(const QModelIndex& index) { emit q_ptr->indexChanged(index.row()); } ListViewItem *ListViewSection::owner() const { return m_pOwner; } void ListView::applyModelChanges(QAbstractItemModel* m) { // Delete the sections while(auto sec = d_ptr->m_pFirstSection) delete sec; d_ptr->m_pFirstSection = nullptr; } #include diff --git a/src/views/treeview.cpp b/src/views/treeview.cpp index 3ed5f0a..015a595 100644 --- a/src/views/treeview.cpp +++ b/src/views/treeview.cpp @@ -1,191 +1,191 @@ /*************************************************************************** * 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 "treeview.h" // KQuickItemViews #include "adapters/abstractitemadapter.h" #include "extensions/contextextension.h" #include "adapters/modeladapter.h" #include "contextadapterfactory.h" #include "adapters/contextadapter.h" // Qt #include /** * Polymorphic tree item for the SingleModelViewBase. * * Classes implementing SingleModelViewBase need to provide an implementation of the pure * virtual functions. It is useful, for example, to manage both a raster and * QQuickItem based version of a view. * * The state is managed by the SingleModelViewBase and it's own protected virtual methods. */ class TreeViewItem final : public AbstractItemAdapter { public: explicit TreeViewItem(Viewport* r); virtual ~TreeViewItem(); // Actions virtual bool move () override; virtual bool remove () override; private: bool m_IsHead { false }; }; /// Add the same property as the QtQuick.ListView class TreeContextProperties final : public ContextExtension { public: virtual ~TreeContextProperties() {} virtual QVector& propertyNames() const override; virtual QVariant getProperty(AbstractItemAdapter* item, uint id, const QModelIndex& index) const override; virtual bool setProperty(AbstractItemAdapter* item, uint id, const QVariant& value, const QModelIndex& index) const override; }; class TreeViewPrivate { public: }; TreeView::TreeView(QQuickItem* parent) : SingleModelViewBase(new ItemFactory(), parent), d_ptr(new TreeViewPrivate) { modelAdapters().first()->contextAdapterFactory()->addContextExtension( new TreeContextProperties() ); } TreeView::~TreeView() { delete d_ptr; } TreeViewItem::TreeViewItem(Viewport* r) : AbstractItemAdapter(r) { } TreeViewItem::~TreeViewItem() { - delete item(); + delete container(); } bool TreeViewItem::move() { // Will happen when trying to move a FAILED, but buffered item - if (!item()) { + if (!container()) { qDebug() << "NO ITEM" << index().data(); return false; } - item()->setWidth(view()->contentItem()->width()); + container()->setWidth(view()->contentItem()->width()); auto nextElem = static_cast(next(Qt::BottomEdge)); auto prevElem = static_cast(next(Qt::TopEdge)); // The root has been moved in the middle of the tree, find the new root //TODO maybe add a deterministic API instead of O(N) lookup if (prevElem && m_IsHead) { m_IsHead = false; auto root = prevElem; while (auto prev = root->next(Qt::TopEdge)) root = static_cast(prev); root->move(); Q_ASSERT(root->m_IsHead); } // So other items can be GCed without always resetting to 0x0, note that it // might be a good idea to extend Flickable to support a virtual // origin point. if ((!prevElem) || (nextElem && nextElem->m_IsHead)) { - auto anchors = qvariant_cast(item()->property("anchors")); + auto anchors = qvariant_cast(container()->property("anchors")); anchors->setProperty("top", {}); - item()->setY(0); + container()->setY(0); m_IsHead = true; } else if (prevElem) { Q_ASSERT(!m_IsHead); - item()->setProperty("y", {}); - auto anchors = qvariant_cast(item()->property("anchors")); - anchors->setProperty("top", prevElem->item()->property("bottom")); + container()->setProperty("y", {}); + auto anchors = qvariant_cast(container()->property("anchors")); + anchors->setProperty("top", prevElem->container()->property("bottom")); } // Now, update the next anchors if (nextElem) { nextElem->m_IsHead = false; - nextElem->item()->setProperty("y", {}); + nextElem->container()->setProperty("y", {}); - auto anchors = qvariant_cast(nextElem->item()->property("anchors")); - anchors->setProperty("top", item()->property("bottom")); + auto anchors = qvariant_cast(nextElem->container()->property("anchors")); + anchors->setProperty("top", container()->property("bottom")); } updateGeometry(); return true; } bool TreeViewItem::remove() { - if (item()) { - item()->setParent(nullptr); - item()->setParentItem(nullptr); - item()->setVisible(false); + if (container()) { + container()->setParent(nullptr); + container()->setParentItem(nullptr); + container()->setVisible(false); } auto nextElem = static_cast(next(Qt::BottomEdge)); auto prevElem = static_cast(next(Qt::TopEdge)); if (nextElem) { if (m_IsHead) { - auto anchors = qvariant_cast(nextElem->item()->property("anchors")); + auto anchors = qvariant_cast(nextElem->container()->property("anchors")); anchors->setProperty("top", {}); - item()->setY(0); + container()->setY(0); nextElem->m_IsHead = true; } else { //TODO maybe eventually use a state machine for this - auto anchors = qvariant_cast(nextElem->item()->property("anchors")); - anchors->setProperty("top", prevElem->item()->property("bottom")); + auto anchors = qvariant_cast(nextElem->container()->property("anchors")); + anchors->setProperty("top", prevElem->container()->property("bottom")); } } return true; } QVector& TreeContextProperties::propertyNames() const { static QVector ret { "expanded" }; return ret; } QVariant TreeContextProperties::getProperty(AbstractItemAdapter* item, uint id, const QModelIndex& index) const { Q_UNUSED(index); Q_ASSERT(id == 0 && item); return !item->isCollapsed(); } bool TreeContextProperties::setProperty(AbstractItemAdapter* item, uint id, const QVariant& value, const QModelIndex& index) const { Q_ASSERT(id == 0 && item && value.canConvert()); item->setCollapsed(!value.toBool()); return true; }