diff --git a/src/qmlwidgets/treetraversalreflector_debug.h b/src/qmlwidgets/treetraversalreflector_debug.h index ddb2d9b6..e714c052 100644 --- a/src/qmlwidgets/treetraversalreflector_debug.h +++ b/src/qmlwidgets/treetraversalreflector_debug.h @@ -1,157 +1,157 @@ /** * While this module is under development and the autotests are lacking, * always run strict tests at runtime. */ -void TreeTraversalReflector::_test_validateTree(TreeTraversalItems* p) +void TreeTraversalReflectorPrivate::_test_validateTree(TreeTraversalItems* p) { #ifdef QT_NO_DEBUG_OUTPUT return; #endif // The asserts below only work on valid models with valid delegates. // If those conditions are not met, it *could* work anyway, but cannot be // validated. /*Q_ASSERT(m_FailedCount >= 0); if (m_FailedCount) { qWarning() << "The tree is fragmented and failed to self heal: disable validation"; return; }*/ - if (p->m_pParent == d_ptr->m_pRoot && d_ptr->m_pRoot->m_tChildren[FIRST] == p) { + if (p->m_pParent == m_pRoot && m_pRoot->m_tChildren[FIRST] == p) { Q_ASSERT(!p->m_pTreeItem->up()); } // First, let's check the linked list to avoid running more test on really // corrupted data if (auto i = p->m_tChildren[FIRST]) { auto idx = i->m_Index; int count = 1; auto oldI = i; while ((oldI = i) && (i = i->m_tSiblings[NEXT])) { // If this is a next, then there has to be a previous Q_ASSERT(i->m_pParent == p); Q_ASSERT(i->m_tSiblings[PREVIOUS]); Q_ASSERT(i->m_tSiblings[PREVIOUS]->m_Index == idx); //Q_ASSERT(i->m_Index.row() == idx.row()+1); //FIXME Q_ASSERT(i->m_tSiblings[PREVIOUS]->m_tSiblings[NEXT] == i); Q_ASSERT(i->m_tSiblings[PREVIOUS] == oldI); idx = i->m_Index; count++; } Q_ASSERT(p == p->m_tChildren[FIRST]->m_pParent); Q_ASSERT(p == p->m_tChildren[LAST]->m_pParent); Q_ASSERT(p->m_hLookup.size() == count); } // Do that again in the other direction if (auto i = p->m_tChildren[LAST]) { auto idx = i->m_Index; auto oldI = i; int count = 1; while ((oldI = i) && (i = i->m_tSiblings[PREVIOUS])) { Q_ASSERT(i->m_tSiblings[NEXT]); Q_ASSERT(i->m_tSiblings[NEXT]->m_Index == idx); Q_ASSERT(i->m_pParent == p); //Q_ASSERT(i->m_Index.row() == idx.row()-1); //FIXME Q_ASSERT(i->m_tSiblings[NEXT]->m_tSiblings[PREVIOUS] == i); Q_ASSERT(i->m_tSiblings[NEXT] == oldI); idx = i->m_Index; count++; } Q_ASSERT(p->m_hLookup.size() == count); } //TODO remove once stable // Brute force recursive validations TreeTraversalItems *old(nullptr), *newest(nullptr); for (auto i = p->m_hLookup.constBegin(); i != p->m_hLookup.constEnd(); i++) { if ((!newest) || i.key().row() < newest->m_Index.row()) newest = i.value(); if ((!old) || i.key().row() > old->m_Index.row()) old = i.value(); // Check that m_FailedCount is valid Q_ASSERT(!i.value()->m_pTreeItem->hasFailed()); // Test the indices - Q_ASSERT(p == d_ptr->m_pRoot || i.key().internalPointer() == i.value()->m_Index.internalPointer()); - Q_ASSERT(p == d_ptr->m_pRoot || (p->m_Index.isValid()) || p->m_Index.internalPointer() != i.key().internalPointer()); + Q_ASSERT(p == m_pRoot || i.key().internalPointer() == i.value()->m_Index.internalPointer()); + Q_ASSERT(p == m_pRoot || (p->m_Index.isValid()) || p->m_Index.internalPointer() != i.key().internalPointer()); //Q_ASSERT(old == i.value() || old->m_Index.row() > i.key().row()); //FIXME //Q_ASSERT(newest == i.value() || newest->m_Index.row() < i.key().row()); //FIXME // Test that there is no trivial duplicate TreeTraversalItems for the same index if(i.value()->m_tSiblings[PREVIOUS] && i.value()->m_tSiblings[PREVIOUS]->m_hLookup.isEmpty()) { const auto prev = i.value()->up(); Q_ASSERT(prev == i.value()->m_tSiblings[PREVIOUS]); const auto next = prev->down(); Q_ASSERT(next == i.value()); } // Test the virtual linked list between the leafs and branches if(auto next = i.value()->down()) { Q_ASSERT(next->up() == i.value()); Q_ASSERT(next != i.value()); } else { // There is always a next is those conditions are not met unless there // is failed elements creating (auto-corrected) holes in the chains. Q_ASSERT(!i.value()->m_tSiblings[NEXT]); Q_ASSERT(i.value()->m_hLookup.isEmpty()); } if(auto prev = i.value()->up()) { Q_ASSERT(prev->down() == i.value()); Q_ASSERT(prev != i.value()); } else { // There is always a previous if those conditions are not met unless there // is failed elements creating (auto-corrected) holes in the chains. Q_ASSERT(!i.value()->m_tSiblings[PREVIOUS]); - Q_ASSERT(i.value()->m_pParent == d_ptr->m_pRoot); + Q_ASSERT(i.value()->m_pParent == m_pRoot); } _test_validateTree(i.value()); } // Traverse as a list - if (p == d_ptr->m_pRoot) { + if (p == m_pRoot) { TreeTraversalItems* oldTTI(nullptr); int count(0), count2(0); - for (auto i = d_ptr->m_pRoot->m_tChildren[FIRST]; i; i = i->down()) { + for (auto i = m_pRoot->m_tChildren[FIRST]; i; i = i->down()) { Q_ASSERT((!oldTTI) || i->up()); Q_ASSERT(i->up() == oldTTI); oldTTI = i; count++; } // Backward too oldTTI = nullptr; - auto last = d_ptr->m_pRoot->m_tChildren[LAST]; + auto last = m_pRoot->m_tChildren[LAST]; while (last && last->m_tChildren[LAST]) last = last->m_tChildren[LAST]; for (auto i = last; i; i = i->up()) { Q_ASSERT((!oldTTI) || i->down()); Q_ASSERT(i->down() == oldTTI); oldTTI = i; count2++; } Q_ASSERT(count == count2); } // Test that the list edges are valid Q_ASSERT(!(!!p->m_tChildren[LAST] ^ !!p->m_tChildren[FIRST])); Q_ASSERT(p->m_tChildren[LAST] == old); Q_ASSERT(p->m_tChildren[FIRST] == newest); Q_ASSERT((!old) || !old->m_tSiblings[NEXT]); Q_ASSERT((!newest) || !newest->m_tSiblings[PREVIOUS]); } diff --git a/src/qmlwidgets/treetraversalreflector_p.cpp b/src/qmlwidgets/treetraversalreflector_p.cpp index bb48fdcc..57424939 100644 --- a/src/qmlwidgets/treetraversalreflector_p.cpp +++ b/src/qmlwidgets/treetraversalreflector_p.cpp @@ -1,1232 +1,1259 @@ /*************************************************************************** * Copyright (C) 2017-2018 by Bluesystems * * 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 "treetraversalreflector_p.h" #include "abstractviewitem_p.h" #include "abstractviewitem.h" // Use some constant for readability #define PREVIOUS 0 #define NEXT 1 #define FIRST 0 #define LAST 1 /** * Hold the QPersistentModelIndex and the metadata associated with them. */ struct TreeTraversalItems { explicit TreeTraversalItems(TreeTraversalItems* parent, TreeTraversalReflectorPrivate* d): m_pParent(parent), d_ptr(d) {} enum class State { BUFFER = 0, /*!< Not in use by any visible indexes, but pre-loaded */ REMOVED = 1, /*!< Currently in a removal transaction */ REACHABLE = 2, /*!< The [grand]parent of visible indexes */ VISIBLE = 3, /*!< The element is visible on screen */ ERROR = 4, /*!< Something went wrong */ DANGLING = 5, /*!< Being destroyed */ }; enum class Action { SHOW = 0, /*!< Make visible on screen (or buffer) */ HIDE = 1, /*!< Remove from the screen (or buffer) */ ATTACH = 2, /*!< Track, but do not show */ DETACH = 3, /*!< Stop tracking for changes */ UPDATE = 4, /*!< Update the element */ MOVE = 5, /*!< Update the depth and lookup */ }; typedef bool(TreeTraversalItems::*StateF)(); static const State m_fStateMap [6][6]; static const StateF m_fStateMachine[6][6]; bool performAction(Action); bool nothing(); bool error (); bool show (); bool hide (); bool attach (); bool detach (); bool refresh(); bool index (); bool destroy(); // Geometric navigation TreeTraversalItems* up () const; TreeTraversalItems* down () const; TreeTraversalItems* left () const; TreeTraversalItems* right() const; // Helpers bool updateVisibility(); //TODO use a btree, not an hash QHash m_hLookup; uint m_Depth {0}; State m_State {State::BUFFER}; // Keep the parent to be able to get back to the root TreeTraversalItems* m_pParent; TreeTraversalItems* m_tSiblings[2] = {nullptr, nullptr}; TreeTraversalItems* m_tChildren[2] = {nullptr, nullptr}; // Because slotRowsMoved is called before the change take effect, cache // the "new real row and column" since the current index()->row() is now // garbage. int m_MoveToRow {-1}; int m_MoveToColumn {-1}; QPersistentModelIndex m_Index; VisualTreeItem* m_pTreeItem {nullptr}; TreeTraversalReflectorPrivate* d_ptr; }; -class TreeTraversalReflectorPrivate +class TreeTraversalReflectorPrivate : public QObject { + Q_OBJECT public: + // Helpers + TreeTraversalItems* addChildren(TreeTraversalItems* parent, const QModelIndex& index); + void bridgeGap(TreeTraversalItems* first, TreeTraversalItems* second, bool insert = false); + void createGap(TreeTraversalItems* first, TreeTraversalItems* last ); + TreeTraversalItems* ttiForIndex(const QModelIndex& idx) const; + + void setTemporaryIndices(const QModelIndex &parent, int start, int end, + const QModelIndex &destination, int row); + void resetTemporaryIndices(const QModelIndex &parent, int start, int end, + const QModelIndex &destination, int row); + TreeTraversalItems* m_pRoot {new TreeTraversalItems(nullptr, this)}; //TODO this is vertical only, make this a 2D vector for H TreeTraversalItems* m_tVisibleTTIRange[2] = {nullptr, nullptr}; /// All elements with loaded children QHash m_hMapper; QAbstractItemModel* m_pModel {nullptr}; std::function m_fFactory; TreeTraversalReflector* q_ptr; + + // Tests + void _test_validateTree(TreeTraversalItems* p); + +public Q_SLOTS: + void cleanup(); + void slotRowsInserted (const QModelIndex& parent, int first, int last); + void slotRowsRemoved (const QModelIndex& parent, int first, int last); + void slotLayoutChanged ( ); + void slotRowsMoved (const QModelIndex &p, int start, int end, + const QModelIndex &dest, int row); + void slotRowsMoved2 (const QModelIndex &p, int start, int end, + const QModelIndex &dest, int row); }; #include #define S TreeTraversalItems::State:: const TreeTraversalItems::State TreeTraversalItems::m_fStateMap[6][6] = { /* SHOW HIDE ATTACH DETACH UPDATE MOVE */ /*BUFFER */ { S VISIBLE, S BUFFER , S REACHABLE, S DANGLING , S BUFFER , S BUFFER }, /*REMOVED */ { S ERROR , S ERROR , S ERROR , S BUFFER , S ERROR , S ERROR }, /*REACHABLE*/ { S VISIBLE, S REACHABLE, S ERROR , S BUFFER , S ERROR , S REACHABLE }, /*VISIBLE */ { S VISIBLE, S REACHABLE, S ERROR , S BUFFER , S VISIBLE, S VISIBLE }, /*ERROR */ { S ERROR , S ERROR , S ERROR , S ERROR , S ERROR , S ERROR }, /*DANGLING */ { S ERROR , S ERROR , S ERROR , S ERROR , S ERROR , S ERROR }, }; #undef S #define A &TreeTraversalItems:: const TreeTraversalItems::StateF TreeTraversalItems::m_fStateMachine[6][6] = { /* SHOW HIDE ATTACH DETACH UPDATE MOVE */ /*BUFFER */ { A show , A nothing, A attach, A destroy , A refresh, A index }, /*REMOVED */ { A error , A error , A error , A detach , A error , A error }, /*REACHABLE*/ { A show , A nothing, A error , A detach , A error , A index }, /*VISIBLE */ { A nothing, A hide , A error , A detach , A refresh, A index }, /*ERROR */ { A error , A error , A error , A error , A error , A error }, /*DANGLING */ { A error , A error , A error , A error , A error , A error }, }; #undef A TreeTraversalItems* TreeTraversalItems::up() const { TreeTraversalItems* ret = nullptr; // Another simple case, there is no parent if (!m_pParent) { Q_ASSERT(!m_Index.parent().isValid()); //TODO remove, no longer true when partial loading is implemented return nullptr; } // The parent has no previous siblings, therefor it is directly above the item if (!m_tSiblings[PREVIOUS]) { // if (m_pParent->m_pParent && m_pParent->m_pParent->m_tChildren[FIRST]) // Q_ASSERT(m_pParent->m_pParent->m_tChildren[FIRST] == m_pParent); //FIXME Q_ASSERT(index().row() == 0); // This is the root, there is no previous element if (!m_pParent->m_pTreeItem) { //Q_ASSERT(!index().parent().isValid()); //Happens when reseting models return nullptr; } ret = m_pParent; // Avoids useless unreadable indentation return ret; } ret = m_tSiblings[PREVIOUS]; while (ret->m_tChildren[LAST]) ret = ret->m_tChildren[LAST]; return ret; } TreeTraversalItems* TreeTraversalItems::down() const { TreeTraversalItems* ret = nullptr; auto i = this; if (m_tChildren[FIRST]) { //Q_ASSERT(m_pParent->m_tChildren[FIRST]->m_Index.row() == 0); //racy ret = m_tChildren[FIRST]; return ret; } // Recursively unwrap the tree until an element is found while(i) { if (i->m_tSiblings[NEXT]) { ret = i->m_tSiblings[NEXT]; return ret; } i = i->m_pParent; } // Can't happen, exists to detect corrupted code if (m_Index.parent().isValid()) { Q_ASSERT(m_pParent); // Q_ASSERT(m_pParent->m_pParent->m_pParent->m_hLookup.size() // == m_pParent->m_Index.parent().row()+1); } return ret; } TreeTraversalItems* TreeTraversalItems::left() const { return nullptr; //TODO } TreeTraversalItems* TreeTraversalItems::right() const { return nullptr; //TODO } bool TreeTraversalItems::performAction(Action a) { const int s = (int)m_State; m_State = m_fStateMap [s][(int)a]; bool ret = (this->*m_fStateMachine[s][(int)a])(); return ret; } bool TreeTraversalItems::nothing() { return true; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn" bool TreeTraversalItems::error() { Q_ASSERT(false); return false; } #pragma GCC diagnostic pop bool TreeTraversalItems::updateVisibility() { //TODO support horizontal visibility const bool isVisible = m_pTreeItem->fitsInView(); qDebug() << "\n\nUPDATE VIS" << isVisible << m_Index.row() << m_Index.data(); if (auto up = m_pTreeItem->up()) { if (isVisible && !up->isVisible()) { d_ptr->m_tVisibleTTIRange[FIRST] = this; } } else if (isVisible) d_ptr->m_tVisibleTTIRange[FIRST] = this; if (auto down = m_pTreeItem->up()) { if (isVisible && !down->isVisible()) { d_ptr->m_tVisibleTTIRange[LAST] = this; } } else if (isVisible) d_ptr->m_tVisibleTTIRange[LAST] = this; return isVisible; } bool TreeTraversalItems::show() { // qDebug() << "SHOW"; if (!m_pTreeItem) { m_pTreeItem = d_ptr->q_ptr->createItem()->s_ptr; Q_ASSERT(m_pTreeItem); m_pTreeItem->m_pTTI = this; } m_pTreeItem->performAction(VisualTreeItem::ViewAction::ENTER_BUFFER); m_pTreeItem->performAction(VisualTreeItem::ViewAction::ENTER_VIEW ); updateVisibility(); return true; } bool TreeTraversalItems::hide() { // qDebug() << "HIDE"; return true; } bool TreeTraversalItems::attach() { if (m_pTreeItem) m_pTreeItem->performAction(VisualTreeItem::ViewAction::ATTACH); // qDebug() << "ATTACH" << (int)m_State; performAction(Action::MOVE); //FIXME don't return performAction(Action::SHOW); //FIXME don't } bool TreeTraversalItems::detach() { // First, detach any remaining children auto i = m_hLookup.begin(); while ((i = m_hLookup.begin()) != m_hLookup.end()) i.value()->performAction(TreeTraversalItems::Action::DETACH); Q_ASSERT(m_hLookup.isEmpty()); if (m_pTreeItem) { // If the item was active (due, for example, of a full reset), then // it has to be removed from view then deleted. if (m_pTreeItem->m_State == VisualTreeItem::State::ACTIVE) { m_pTreeItem->performAction(VisualTreeItem::ViewAction::DETACH); // It should still exists, it may crash otherwise, so make sure early Q_ASSERT(m_pTreeItem->m_State == VisualTreeItem::State::POOLED); m_pTreeItem->m_State = VisualTreeItem::State::POOLED; //FIXME ^^ add a new action for finish pooling or call // VisualTreeItem::detach from a new action method (instead of directly) } m_pTreeItem->performAction(VisualTreeItem::ViewAction::DETACH); m_pTreeItem = nullptr; } if (m_pParent) { const int size = m_pParent->m_hLookup.size(); m_pParent->m_hLookup.remove(m_Index); Q_ASSERT(size == m_pParent->m_hLookup.size()+1); } if (m_tSiblings[PREVIOUS] || m_tSiblings[NEXT]) { if (m_tSiblings[PREVIOUS]) m_tSiblings[PREVIOUS]->m_tSiblings[NEXT] = m_tSiblings[NEXT]; if (m_tSiblings[NEXT]) m_tSiblings[NEXT]->m_tSiblings[PREVIOUS] = m_tSiblings[PREVIOUS]; } else if (m_pParent) { //FIXME very wrong Q_ASSERT(m_pParent->m_hLookup.isEmpty()); m_pParent->m_tChildren[FIRST] = nullptr; m_pParent->m_tChildren[LAST] = nullptr; } //FIXME set the parent m_tChildren[FIRST] correctly and add an insert()/move() method // then drop bridgeGap return true; } bool TreeTraversalItems::refresh() { // // qDebug() << "REFRESH"; for (auto i = m_tChildren[FIRST]; i; i = i->m_tSiblings[NEXT]) { Q_ASSERT(i); Q_ASSERT(i != this); i->performAction(TreeTraversalItems::Action::UPDATE); if (i == m_tChildren[LAST]) break; } return true; } bool TreeTraversalItems::index() { //TODO remove this implementation and use the one below // Propagate for (auto i = m_tChildren[FIRST]; i; i = i->m_tSiblings[NEXT]) { Q_ASSERT(i); Q_ASSERT(i != this); i->performAction(TreeTraversalItems::Action::MOVE); if (i == m_tChildren[LAST]) break; } //FIXME this if should not exists, this should be handled by the state // machine. if (m_pTreeItem) { m_pTreeItem->performAction(VisualTreeItem::ViewAction::MOVE); //FIXME don't updateVisibility(); //FIXME add a new state change for this } return true; // if (!m_pTreeItem) // return true; //FIXME this is better, but require having createItem() called earlier // if (m_pTreeItem) // m_pTreeItem->performAction(VisualTreeItem::ViewAction::MOVE); //FIXME don't // // if (oldGeo != m_pTreeItem->geometry()) // if (auto n = m_pTreeItem->down()) // n->m_pParent->performAction(TreeTraversalItems::Action::MOVE); // return true; } bool TreeTraversalItems::destroy() { detach(); m_pTreeItem = nullptr; Q_ASSERT(m_hLookup.isEmpty()); delete this; return true; } TreeTraversalReflector::TreeTraversalReflector(QObject* parent) : QObject(parent), d_ptr(new TreeTraversalReflectorPrivate()) { d_ptr->q_ptr = this; } TreeTraversalReflector::~TreeTraversalReflector() { delete d_ptr->m_pRoot; } VisualTreeItem* TreeTraversalReflector::getCorner(TreeTraversalRange* r, Qt::Corner c) const { switch(c) { case Qt::TopLeftCorner: return d_ptr->m_tVisibleTTIRange[FIRST] ? d_ptr->m_tVisibleTTIRange[FIRST]->m_pTreeItem : nullptr; case Qt::BottomLeftCorner: return d_ptr->m_tVisibleTTIRange[LAST] ? d_ptr->m_tVisibleTTIRange[LAST]->m_pTreeItem : nullptr; case Qt::TopRightCorner: case Qt::BottomRightCorner: break; } Q_ASSERT(false); return {}; } // Setters void TreeTraversalReflector::setItemFactory(std::function factory) { d_ptr->m_fFactory = factory; } // factory AbstractViewItem* TreeTraversalReflector::createItem() const { Q_ASSERT(d_ptr->m_fFactory); return d_ptr->m_fFactory(); } -void TreeTraversalReflector::slotRowsInserted(const QModelIndex& parent, int first, int last) +void TreeTraversalReflectorPrivate::slotRowsInserted(const QModelIndex& parent, int first, int last) { - Q_ASSERT((!parent.isValid()) || parent.model() == model()); + Q_ASSERT((!parent.isValid()) || parent.model() == q_ptr->model()); // qDebug() << "\n\nADD" << first << last; - if (!isActive(parent, first, last)) + if (!q_ptr->isActive(parent, first, last)) return; // qDebug() << "\n\nADD2" << q_ptr->width() << q_ptr->height(); - auto pitem = parent.isValid() ? d_ptr->m_hMapper.value(parent) : d_ptr->m_pRoot; + auto pitem = parent.isValid() ? m_hMapper.value(parent) : m_pRoot; TreeTraversalItems *prev(nullptr); //FIXME use up() if (first && pitem) - prev = pitem->m_hLookup.value(model()->index(first-1, 0, parent)); + prev = pitem->m_hLookup.value(q_ptr->model()->index(first-1, 0, parent)); //FIXME support smaller ranges for (int i = first; i <= last; i++) { - auto idx = model()->index(i, 0, parent); + auto idx = q_ptr->model()->index(i, 0, parent); Q_ASSERT(idx.isValid()); Q_ASSERT(idx.parent() != idx); - Q_ASSERT(idx.model() == model()); + Q_ASSERT(idx.model() == q_ptr->model()); auto e = addChildren(pitem, idx); // Keep a dual chained linked list between the visual elements e->m_tSiblings[PREVIOUS] = prev ? prev : nullptr; //FIXME incorrect //FIXME It can happen if the previous is out of the visible range Q_ASSERT( e->m_tSiblings[PREVIOUS] || e->m_Index.row() == 0); //TODO merge with bridgeGap if (prev) bridgeGap(prev, e, true); // This is required before ::ATTACH because otherwise ::down() wont work if ((!pitem->m_tChildren[FIRST]) || e->m_Index.row() <= pitem->m_tChildren[FIRST]->m_Index.row()) { e->m_tSiblings[NEXT] = pitem->m_tChildren[FIRST]; pitem->m_tChildren[FIRST] = e; } e->performAction(TreeTraversalItems::Action::ATTACH); if ((!pitem->m_tChildren[FIRST]) || e->m_Index.row() <= pitem->m_tChildren[FIRST]->m_Index.row()) { Q_ASSERT(pitem != e); if (auto pe = e->up()) pe->performAction(TreeTraversalItems::Action::MOVE); } if ((!pitem->m_tChildren[LAST]) || e->m_Index.row() > pitem->m_tChildren[LAST]->m_Index.row()) { Q_ASSERT(pitem != e); pitem->m_tChildren[LAST] = e; if (auto ne = e->down()) ne->performAction(TreeTraversalItems::Action::MOVE); } - if (auto rc = model()->rowCount(idx)) + if (auto rc = q_ptr->model()->rowCount(idx)) slotRowsInserted(idx, 0, rc-1); prev = e; } if ((!pitem->m_tChildren[LAST]) || last > pitem->m_tChildren[LAST]->m_Index.row()) pitem->m_tChildren[LAST] = prev; Q_ASSERT(pitem->m_tChildren[LAST]); //FIXME use down() - if (model()->rowCount(parent) > last) { - if (auto i = pitem->m_hLookup.value(model()->index(last+1, 0, parent))) { + if (q_ptr->model()->rowCount(parent) > last) { + if (auto i = pitem->m_hLookup.value(q_ptr->model()->index(last+1, 0, parent))) { i->m_tSiblings[PREVIOUS] = prev; prev->m_tSiblings[NEXT] = i; } // else //FIXME it happens // Q_ASSERT(false); } //FIXME EVIL and useless - d_ptr->m_pRoot->performAction(TreeTraversalItems::Action::MOVE); + m_pRoot->performAction(TreeTraversalItems::Action::MOVE); - _test_validateTree(d_ptr->m_pRoot); + _test_validateTree(m_pRoot); - Q_EMIT contentChanged(); + Q_EMIT q_ptr->contentChanged(); if (!parent.isValid()) - Q_EMIT countChanged(); + Q_EMIT q_ptr->countChanged(); } -void TreeTraversalReflector::slotRowsRemoved(const QModelIndex& parent, int first, int last) +void TreeTraversalReflectorPrivate::slotRowsRemoved(const QModelIndex& parent, int first, int last) { - Q_ASSERT((!parent.isValid()) || parent.model() == model()); - Q_EMIT contentChanged(); + Q_ASSERT((!parent.isValid()) || parent.model() == q_ptr->model()); + Q_EMIT q_ptr->contentChanged(); - if (!isActive(parent, first, last)) + if (!q_ptr->isActive(parent, first, last)) return; - auto pitem = parent.isValid() ? d_ptr->m_hMapper.value(parent) : d_ptr->m_pRoot; + auto pitem = parent.isValid() ? m_hMapper.value(parent) : m_pRoot; //TODO make sure the state machine support them //TreeTraversalItems *prev(nullptr), *next(nullptr); //FIXME use up() //if (first && pitem) // prev = pitem->m_hLookup.value(model()->index(first-1, 0, parent)); //next = pitem->m_hLookup.value(model()->index(last+1, 0, parent)); //FIXME support smaller ranges for (int i = first; i <= last; i++) { - auto idx = model()->index(i, 0, parent); + auto idx = q_ptr->model()->index(i, 0, parent); auto elem = pitem->m_hLookup.value(idx); Q_ASSERT(elem); elem->performAction(TreeTraversalItems::Action::DETACH); } if (!parent.isValid()) - Q_EMIT countChanged(); + Q_EMIT q_ptr->countChanged(); } -void TreeTraversalReflector::slotLayoutChanged() +void TreeTraversalReflectorPrivate::slotLayoutChanged() { - if (auto rc = model()->rowCount()) + if (auto rc = q_ptr->model()->rowCount()) slotRowsInserted({}, 0, rc - 1); - Q_EMIT contentChanged(); + Q_EMIT q_ptr->contentChanged(); - Q_EMIT countChanged(); + Q_EMIT q_ptr->countChanged(); } -void TreeTraversalReflector::createGap(TreeTraversalItems* first, TreeTraversalItems* last) +void TreeTraversalReflectorPrivate::createGap(TreeTraversalItems* first, TreeTraversalItems* last) { Q_ASSERT(first->m_pParent == last->m_pParent); if (first->m_tSiblings[PREVIOUS]) { first->m_tSiblings[PREVIOUS]->m_tSiblings[NEXT] = last->m_tSiblings[NEXT]; } if (last->m_tSiblings[NEXT]) { last->m_tSiblings[NEXT]->m_tSiblings[PREVIOUS] = first->m_tSiblings[PREVIOUS]; } if (first->m_pParent->m_tChildren[FIRST] == first) first->m_pParent->m_tChildren[FIRST] = last->m_tSiblings[NEXT]; if (last->m_pParent->m_tChildren[LAST] == last) last->m_pParent->m_tChildren[LAST] = first->m_tSiblings[PREVIOUS]; Q_ASSERT((!first->m_tSiblings[PREVIOUS]) || first->m_tSiblings[PREVIOUS]->down() != first); Q_ASSERT((!last->m_tSiblings[NEXT]) || last->m_tSiblings[NEXT]->up() != last); Q_ASSERT((!first) || first->m_tChildren[FIRST] || first->m_hLookup.isEmpty()); Q_ASSERT((!last) || last->m_tChildren[FIRST] || last->m_hLookup.isEmpty()); // Do not leave invalid pointers for easier debugging last->m_tSiblings[NEXT] = nullptr; first->m_tSiblings[PREVIOUS] = nullptr; } /// Fix the issues introduced by createGap (does not update m_pParent and m_hLookup) -void TreeTraversalReflector::bridgeGap(TreeTraversalItems* first, TreeTraversalItems* second, bool insert) +void TreeTraversalReflectorPrivate::bridgeGap(TreeTraversalItems* first, TreeTraversalItems* second, bool insert) { // 3 possible case: siblings, first child or last child if (first && second && first->m_pParent == second->m_pParent) { // first and second are siblings // Assume the second item is new if (insert && first->m_tSiblings[NEXT]) { second->m_tSiblings[NEXT] = first->m_tSiblings[NEXT]; first->m_tSiblings[NEXT]->m_tSiblings[PREVIOUS] = second; } first->m_tSiblings[NEXT] = second; second->m_tSiblings[PREVIOUS] = first; } else if (second && ((!first) || first == second->m_pParent)) { // The `second` is `first` first child or it's the new root second->m_tSiblings[PREVIOUS] = nullptr; if (!second->m_pParent->m_tChildren[LAST]) second->m_pParent->m_tChildren[LAST] = second; second->m_tSiblings[NEXT] = second->m_pParent->m_tChildren[FIRST]; if (second->m_pParent->m_tChildren[FIRST]) { second->m_pParent->m_tChildren[FIRST]->m_tSiblings[PREVIOUS] = second; } second->m_pParent->m_tChildren[FIRST] = second; //BEGIN test /*int count =0; for (auto c = second->m_pParent->m_tChildren[FIRST]; c; c = c->m_tSiblings[NEXT]) count++; Q_ASSERT(count == second->m_pParent->m_hLookup.size());*/ //END test } else if (first) { // It's the last element or the second is a last leaf and first is unrelated first->m_tSiblings[NEXT] = nullptr; if (!first->m_pParent->m_tChildren[FIRST]) first->m_pParent->m_tChildren[FIRST] = first; if (first->m_pParent->m_tChildren[LAST] && first->m_pParent->m_tChildren[LAST] != first) { first->m_pParent->m_tChildren[LAST]->m_tSiblings[NEXT] = first; first->m_tSiblings[PREVIOUS] = first->m_pParent->m_tChildren[LAST]; } first->m_pParent->m_tChildren[LAST] = first; //BEGIN test int count =0; for (auto c = first->m_pParent->m_tChildren[LAST]; c; c = c->m_tSiblings[PREVIOUS]) count++; Q_ASSERT(first->m_pParent->m_tChildren[FIRST]); Q_ASSERT(count == first->m_pParent->m_hLookup.size()); //END test } else { Q_ASSERT(false); //Something went really wrong elsewhere } if (first) Q_ASSERT(first->m_pParent->m_tChildren[FIRST]); if (second) Q_ASSERT(second->m_pParent->m_tChildren[FIRST]); if (first) Q_ASSERT(first->m_pParent->m_tChildren[LAST]); if (second) Q_ASSERT(second->m_pParent->m_tChildren[LAST]); // if (first && second) { //Need to disable other asserts in down() // Q_ASSERT(first->down() == second); // Q_ASSERT(second->up() == first); // } } -void TreeTraversalReflector::setTemporaryIndices(const QModelIndex &parent, int start, int end, +void TreeTraversalReflectorPrivate::setTemporaryIndices(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) { //FIXME list only // Before moving them, set a temporary now/col value because it wont be set // on the index until before slotRowsMoved2 is called (but after this // method returns //TODO do not use the hashmap, it is already known if (parent == destination) { - const auto pitem = parent.isValid() ? d_ptr->m_hMapper.value(parent) : d_ptr->m_pRoot; + const auto pitem = parent.isValid() ? m_hMapper.value(parent) : m_pRoot; for (int i = start; i <= end; i++) { - auto idx = model()->index(i, 0, parent); + auto idx = q_ptr->model()->index(i, 0, parent); auto elem = pitem->m_hLookup.value(idx); Q_ASSERT(elem); elem->m_MoveToRow = row + (i - start); } for (int i = row; i <= row + (end - start); i++) { - auto idx = model()->index(i, 0, parent); + auto idx = q_ptr->model()->index(i, 0, parent); auto elem = pitem->m_hLookup.value(idx); Q_ASSERT(elem); elem->m_MoveToRow = row + (end - start) + 1; } } } -void TreeTraversalReflector::resetTemporaryIndices(const QModelIndex &parent, int start, int end, +void TreeTraversalReflectorPrivate::resetTemporaryIndices(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) { //FIXME list only // Before moving them, set a temporary now/col value because it wont be set // on the index until before slotRowsMoved2 is called (but after this // method returns //TODO do not use the hashmap, it is already known if (parent == destination) { - const auto pitem = parent.isValid() ? d_ptr->m_hMapper.value(parent) : d_ptr->m_pRoot; + const auto pitem = parent.isValid() ? m_hMapper.value(parent) : m_pRoot; for (int i = start; i <= end; i++) { - auto idx = model()->index(i, 0, parent); + auto idx = q_ptr->model()->index(i, 0, parent); auto elem = pitem->m_hLookup.value(idx); Q_ASSERT(elem); elem->m_MoveToRow = -1; } for (int i = row; i <= row + (end - start); i++) { - auto idx = model()->index(i, 0, parent); + auto idx = q_ptr->model()->index(i, 0, parent); auto elem = pitem->m_hLookup.value(idx); Q_ASSERT(elem); elem->m_MoveToRow = -1; } } } -void TreeTraversalReflector::slotRowsMoved(const QModelIndex &parent, int start, int end, +void TreeTraversalReflectorPrivate::slotRowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) { - Q_ASSERT((!parent.isValid()) || parent.model() == model()); - Q_ASSERT((!destination.isValid()) || destination.model() == model()); + Q_ASSERT((!parent.isValid()) || parent.model() == q_ptr->model()); + Q_ASSERT((!destination.isValid()) || destination.model() == q_ptr->model()); // There is literally nothing to do if (parent == destination && start == row) return; // Whatever has to be done only affect a part that's not currently tracked. - if ((!isActive(parent, start, end)) && !isActive(destination, row, row+(end-start))) + if ((!q_ptr->isActive(parent, start, end)) && !q_ptr->isActive(destination, row, row+(end-start))) return; setTemporaryIndices(parent, start, end, destination, row); //TODO also support trees // As the actual view is implemented as a daisy chained list, only moving // the edges is necessary for the TreeTraversalItems. Each VisualTreeItem // need to be moved. - const auto idxStart = model()->index(start, 0, parent); - const auto idxEnd = model()->index(end , 0, parent); + const auto idxStart = q_ptr->model()->index(start, 0, parent); + const auto idxEnd = q_ptr->model()->index(end , 0, parent); Q_ASSERT(idxStart.isValid() && idxEnd.isValid()); //FIXME once partial ranges are supported, this is no longer always valid auto startTTI = ttiForIndex(idxStart); auto endTTI = ttiForIndex(idxEnd); if (end - start == 1) Q_ASSERT(startTTI->m_tSiblings[NEXT] == endTTI); //FIXME so I don't forget, it will mess things up if silently ignored Q_ASSERT(startTTI && endTTI); Q_ASSERT(startTTI->m_pParent == endTTI->m_pParent); auto oldPreviousTTI = startTTI->up(); auto oldNextTTI = endTTI->down(); Q_ASSERT((!oldPreviousTTI) || oldPreviousTTI->down() == startTTI); Q_ASSERT((!oldNextTTI) || oldNextTTI->up() == endTTI); - auto newNextIdx = model()->index(row, 0, destination); + auto newNextIdx = q_ptr->model()->index(row, 0, destination); // You cannot move things into an empty model Q_ASSERT((!row) || newNextIdx.isValid()); TreeTraversalItems *newNextTTI(nullptr), *newPrevTTI(nullptr); // Rewind until a next element is found, this happens when destination is empty if (!newNextIdx.isValid() && destination.parent().isValid()) { - Q_ASSERT(model()->rowCount(destination) == row); + Q_ASSERT(q_ptr->model()->rowCount(destination) == row); auto par = destination.parent(); do { - if (model()->rowCount(par.parent()) > par.row()) { - newNextIdx = model()->index(par.row(), 0, par.parent()); + if (q_ptr->model()->rowCount(par.parent()) > par.row()) { + newNextIdx = q_ptr->model()->index(par.row(), 0, par.parent()); break; } par = par.parent(); } while (par.isValid()); newNextTTI = ttiForIndex(newNextIdx); } else { newNextTTI = ttiForIndex(newNextIdx); newPrevTTI = newNextTTI ? newNextTTI->up() : nullptr; } if (!row) { auto otherI = ttiForIndex(destination); Q_ASSERT((!newPrevTTI) || otherI == newPrevTTI); } // When there is no next element, then the parent has to be extracted manually if (!(newNextTTI || newPrevTTI)) { if (!row) newPrevTTI = ttiForIndex(destination); else { newPrevTTI = ttiForIndex( destination.model()->index(row-1, 0, destination) ); } } Q_ASSERT((newPrevTTI || startTTI) && newPrevTTI != startTTI); Q_ASSERT((newNextTTI || endTTI ) && newNextTTI != endTTI ); TreeTraversalItems* newParentTTI = ttiForIndex(destination); - newParentTTI = newParentTTI ? newParentTTI : d_ptr->m_pRoot; + newParentTTI = newParentTTI ? newParentTTI : m_pRoot; auto oldParentTTI = startTTI->m_pParent; // Make sure not to leave invalid pointers while the steps below are being performed createGap(startTTI, endTTI); // Update the tree parent (if necessary) if (oldParentTTI != newParentTTI) { for (auto i = startTTI; i; i = i->m_tSiblings[NEXT]) { auto idx = i->m_Index; const int size = oldParentTTI->m_hLookup.size(); oldParentTTI->m_hLookup.remove(idx); Q_ASSERT(oldParentTTI->m_hLookup.size() == size-1); newParentTTI->m_hLookup[idx] = i; i->m_pParent = newParentTTI; if (i == endTTI) break; } } Q_ASSERT(startTTI->m_pParent == newParentTTI); Q_ASSERT(endTTI->m_pParent == newParentTTI); bridgeGap(newPrevTTI, startTTI ); bridgeGap(endTTI , newNextTTI); // Close the gap between the old previous and next elements Q_ASSERT(startTTI->m_tSiblings[NEXT] != startTTI); Q_ASSERT(startTTI->m_tSiblings[PREVIOUS] != startTTI); Q_ASSERT(endTTI->m_tSiblings[NEXT] != endTTI ); Q_ASSERT(endTTI->m_tSiblings[PREVIOUS] != endTTI ); //BEGIN debug if (newPrevTTI) { int count = 0; for (auto c = newPrevTTI->m_pParent->m_tChildren[FIRST]; c; c = c->m_tSiblings[NEXT]) count++; Q_ASSERT(count == newPrevTTI->m_pParent->m_hLookup.size()); count = 0; for (auto c = newPrevTTI->m_pParent->m_tChildren[LAST]; c; c = c->m_tSiblings[PREVIOUS]) count++; Q_ASSERT(count == newPrevTTI->m_pParent->m_hLookup.size()); } //END bridgeGap(oldPreviousTTI, oldNextTTI); if (endTTI->m_tSiblings[NEXT]) { Q_ASSERT(endTTI->m_tSiblings[NEXT]->m_tSiblings[PREVIOUS] == endTTI); } if (startTTI->m_tSiblings[PREVIOUS]) { Q_ASSERT(startTTI->m_tSiblings[PREVIOUS]->m_pParent == startTTI->m_pParent); Q_ASSERT(startTTI->m_tSiblings[PREVIOUS]->m_tSiblings[NEXT] == startTTI); } // Q_ASSERT((!newNextVI) || newNextVI->m_pParent->m_tSiblings[PREVIOUS] == endVI->m_pParent); // Q_ASSERT((!newPrevVI) || // // newPrevVI->m_pParent->m_tSiblings[NEXT] == startVI->m_pParent || // (newPrevVI->m_pParent->m_tChildren[FIRST] == startVI->m_pParent && !row) // ); // Q_ASSERT((!oldPreviousVI) || (!oldPreviousVI->m_pParent->m_tSiblings[NEXT]) || // oldPreviousVI->m_pParent->m_tSiblings[NEXT] == (oldNextVI ? oldNextVI->m_pParent : nullptr)); // Move everything //TODO move it more efficient - d_ptr->m_pRoot->performAction(TreeTraversalItems::Action::MOVE); + m_pRoot->performAction(TreeTraversalItems::Action::MOVE); resetTemporaryIndices(parent, start, end, destination, row); } -void TreeTraversalReflector::slotRowsMoved2(const QModelIndex &parent, int start, int end, +void TreeTraversalReflectorPrivate::slotRowsMoved2(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) { Q_UNUSED(parent) Q_UNUSED(start) Q_UNUSED(end) Q_UNUSED(destination) Q_UNUSED(row) // The test would fail if it was in aboutToBeMoved - _test_validateTree(d_ptr->m_pRoot); + _test_validateTree(m_pRoot); } QAbstractItemModel* TreeTraversalReflector::model() const { return d_ptr->m_pModel; } void TreeTraversalReflector::setModel(QAbstractItemModel* m) { if (m == model()) return; if (auto oldM = model()) { - disconnect(oldM, &QAbstractItemModel::rowsInserted, this, - &TreeTraversalReflector::slotRowsInserted); - disconnect(oldM, &QAbstractItemModel::rowsAboutToBeRemoved, this, - &TreeTraversalReflector::slotRowsRemoved); - disconnect(oldM, &QAbstractItemModel::layoutAboutToBeChanged, this, - &TreeTraversalReflector::cleanup); - disconnect(oldM, &QAbstractItemModel::layoutChanged, this, - &TreeTraversalReflector::slotLayoutChanged); - disconnect(oldM, &QAbstractItemModel::modelAboutToBeReset, this, - &TreeTraversalReflector::cleanup); - disconnect(oldM, &QAbstractItemModel::modelReset, this, - &TreeTraversalReflector::slotLayoutChanged); - disconnect(oldM, &QAbstractItemModel::rowsAboutToBeMoved, this, - &TreeTraversalReflector::slotRowsMoved); - disconnect(oldM, &QAbstractItemModel::rowsMoved, this, - &TreeTraversalReflector::slotRowsMoved2); - - slotRowsRemoved({}, 0, oldM->rowCount()-1); + disconnect(oldM, &QAbstractItemModel::rowsInserted, d_ptr, + &TreeTraversalReflectorPrivate::slotRowsInserted); + disconnect(oldM, &QAbstractItemModel::rowsAboutToBeRemoved, d_ptr, + &TreeTraversalReflectorPrivate::slotRowsRemoved); + disconnect(oldM, &QAbstractItemModel::layoutAboutToBeChanged, d_ptr, + &TreeTraversalReflectorPrivate::cleanup); + disconnect(oldM, &QAbstractItemModel::layoutChanged, d_ptr, + &TreeTraversalReflectorPrivate::slotLayoutChanged); + disconnect(oldM, &QAbstractItemModel::modelAboutToBeReset, d_ptr, + &TreeTraversalReflectorPrivate::cleanup); + disconnect(oldM, &QAbstractItemModel::modelReset, d_ptr, + &TreeTraversalReflectorPrivate::slotLayoutChanged); + disconnect(oldM, &QAbstractItemModel::rowsAboutToBeMoved, d_ptr, + &TreeTraversalReflectorPrivate::slotRowsMoved); + disconnect(oldM, &QAbstractItemModel::rowsMoved, d_ptr, + &TreeTraversalReflectorPrivate::slotRowsMoved2); + + d_ptr->slotRowsRemoved({}, 0, oldM->rowCount()-1); } d_ptr->m_hMapper.clear(); delete d_ptr->m_pRoot; d_ptr->m_pRoot = new TreeTraversalItems(nullptr, d_ptr); d_ptr->m_pModel = m; if (!m) return; - connect(model(), &QAbstractItemModel::rowsInserted, this, - &TreeTraversalReflector::slotRowsInserted ); - connect(model(), &QAbstractItemModel::rowsAboutToBeRemoved, this, - &TreeTraversalReflector::slotRowsRemoved ); - connect(model(), &QAbstractItemModel::layoutAboutToBeChanged, this, - &TreeTraversalReflector::cleanup); - connect(model(), &QAbstractItemModel::layoutChanged, this, - &TreeTraversalReflector::slotLayoutChanged); - connect(model(), &QAbstractItemModel::modelAboutToBeReset, this, - &TreeTraversalReflector::cleanup); - connect(model(), &QAbstractItemModel::modelReset, this, - &TreeTraversalReflector::slotLayoutChanged); - connect(model(), &QAbstractItemModel::rowsAboutToBeMoved, this, - &TreeTraversalReflector::slotRowsMoved); - connect(model(), &QAbstractItemModel::rowsMoved, this, - &TreeTraversalReflector::slotRowsMoved2); + connect(model(), &QAbstractItemModel::rowsInserted, d_ptr, + &TreeTraversalReflectorPrivate::slotRowsInserted ); + connect(model(), &QAbstractItemModel::rowsAboutToBeRemoved, d_ptr, + &TreeTraversalReflectorPrivate::slotRowsRemoved ); + connect(model(), &QAbstractItemModel::layoutAboutToBeChanged, d_ptr, + &TreeTraversalReflectorPrivate::cleanup); + connect(model(), &QAbstractItemModel::layoutChanged, d_ptr, + &TreeTraversalReflectorPrivate::slotLayoutChanged); + connect(model(), &QAbstractItemModel::modelAboutToBeReset, d_ptr, + &TreeTraversalReflectorPrivate::cleanup); + connect(model(), &QAbstractItemModel::modelReset, d_ptr, + &TreeTraversalReflectorPrivate::slotLayoutChanged); + connect(model(), &QAbstractItemModel::rowsAboutToBeMoved, d_ptr, + &TreeTraversalReflectorPrivate::slotRowsMoved); + connect(model(), &QAbstractItemModel::rowsMoved, d_ptr, + &TreeTraversalReflectorPrivate::slotRowsMoved2); } void TreeTraversalReflector::populate() { if (!model()) return; if (auto rc = model()->rowCount()) - slotRowsInserted({}, 0, rc - 1); + d_ptr->slotRowsInserted({}, 0, rc - 1); } /// Return true if the indices affect the current view bool TreeTraversalReflector::isActive(const QModelIndex& parent, int first, int last) { return true; //FIXME /*if (m_State == State::UNFILLED) return true; //FIXME only insert elements with loaded children into d_ptr->m_hMapper auto pitem = parent.isValid() ? d_ptr->m_hMapper.value(parent) : d_ptr->m_pRoot; if (parent.isValid() && pitem == d_ptr->m_pRoot) return false; if ((!pitem->m_tChildren[LAST]) || (!pitem->m_tChildren[FIRST])) return true; if (pitem->m_tChildren[LAST]->m_Index.row() >= first) return true; if (pitem->m_tChildren[FIRST]->m_Index.row() <= last) return true; return false;*/ } /// Add new entries to the mapping -TreeTraversalItems* TreeTraversalReflector::addChildren(TreeTraversalItems* parent, const QModelIndex& index) +TreeTraversalItems* TreeTraversalReflectorPrivate::addChildren(TreeTraversalItems* parent, const QModelIndex& index) { Q_ASSERT(index.isValid()); Q_ASSERT(index.parent() != index); - auto e = new TreeTraversalItems(parent, d_ptr); + auto e = new TreeTraversalItems(parent, this); e->m_Index = index; - const int oldSize(d_ptr->m_hMapper.size()), oldSize2(parent->m_hLookup.size()); + const int oldSize(m_hMapper.size()), oldSize2(parent->m_hLookup.size()); - d_ptr->m_hMapper [index] = e; + m_hMapper [index] = e; parent->m_hLookup[index] = e; // If the size did not grow, something leaked - Q_ASSERT(d_ptr->m_hMapper.size() == oldSize+1); + Q_ASSERT(m_hMapper.size() == oldSize+1); Q_ASSERT(parent->m_hLookup.size() == oldSize2+1); return e; } -void TreeTraversalReflector::cleanup() +void TreeTraversalReflectorPrivate::cleanup() { - d_ptr->m_pRoot->performAction(TreeTraversalItems::Action::DETACH); + m_pRoot->performAction(TreeTraversalItems::Action::DETACH); - d_ptr->m_hMapper.clear(); - d_ptr->m_pRoot = new TreeTraversalItems(nullptr, d_ptr); + m_hMapper.clear(); + m_pRoot = new TreeTraversalItems(nullptr, this); //m_FailedCount = 0; } -TreeTraversalItems* TreeTraversalReflector::ttiForIndex(const QModelIndex& idx) const +TreeTraversalItems* TreeTraversalReflectorPrivate::ttiForIndex(const QModelIndex& idx) const { if (!idx.isValid()) return nullptr; if (!idx.parent().isValid()) - return d_ptr->m_pRoot->m_hLookup.value(idx); + return m_pRoot->m_hLookup.value(idx); - if (auto parent = d_ptr->m_hMapper.value(idx.parent())) + if (auto parent = m_hMapper.value(idx.parent())) return parent->m_hLookup.value(idx); return nullptr; } VisualTreeItem* TreeTraversalReflector::parentTreeItem(const QModelIndex& index) const { - if (auto i = ttiForIndex(index)) { + if (auto i = d_ptr->ttiForIndex(index)) { if (i->m_pParent && i->m_pParent->m_pTreeItem) return i->m_pParent->m_pTreeItem; } return nullptr; } AbstractViewItem* TreeTraversalReflector::itemForIndex(const QModelIndex& idx) const { - const auto tti = ttiForIndex(idx); + const auto tti = d_ptr->ttiForIndex(idx); return tti ? tti->m_pTreeItem->d_ptr : nullptr; } bool TreeTraversalReflector::addRange(TreeTraversalRange* range) { // return false; } bool TreeTraversalReflector::removeRange(TreeTraversalRange* range) { //TODO return false; } QList TreeTraversalReflector::ranges() const { return {}; //TODO } bool VisualTreeItem::isVisible() const { return m_pTTI->m_State == TreeTraversalItems::State::VISIBLE; } QPersistentModelIndex VisualTreeItem::index() const { return m_pTTI->m_Index; } /** * Flatten the tree as a linked list. * * Returns the previous non-failed item. */ VisualTreeItem* VisualTreeItem::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 ); const auto ret = m_pTTI->up(); //TODO support collapsed nodes // 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. if (ret && !ret->m_pTreeItem) return ret->m_pTreeItem->up(); //FIXME loop until it's found return ret ? ret->m_pTreeItem : nullptr; } /** * Flatten the tree as a linked list. * * Returns the next non-failed item. */ VisualTreeItem* VisualTreeItem::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 ); const auto ret = m_pTTI->down(); //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. if (ret && !ret->m_pTreeItem) return down(); //FIXME loop return ret ? ret->m_pTreeItem : nullptr; } int VisualTreeItem::row() const { return m_pTTI->m_MoveToRow == -1 ? index().row() : m_pTTI->m_MoveToRow; } int VisualTreeItem::column() const { return m_pTTI->m_MoveToColumn == -1 ? index().column() : m_pTTI->m_MoveToColumn; } //TODO remove void TreeTraversalReflector::refreshEverything() { for (auto i = d_ptr->m_pRoot->m_hLookup.constBegin(); i != d_ptr->m_pRoot->m_hLookup.constEnd(); i++) i.value()->performAction(TreeTraversalItems::Action::UPDATE); } //TODO remove void TreeTraversalReflector::moveEverything() { d_ptr->m_pRoot->performAction(TreeTraversalItems::Action::MOVE); } //TODO remove void TreeTraversalReflector::reloadRange(const QModelIndex& idx) { - if (auto p = ttiForIndex(idx)) { + if (auto p = d_ptr->ttiForIndex(idx)) { auto c = p->m_tChildren[FIRST]; while (c && c != p->m_tChildren[LAST]) { if (c->m_pTreeItem) { c->m_pTreeItem->performAction( VisualTreeItem::ViewAction::UPDATE ); c->m_pTreeItem->performAction( VisualTreeItem::ViewAction::MOVE ); } c = c->m_tSiblings[NEXT]; } } } #undef PREVIOUS #undef NEXT #undef FIRST #undef LAST + +#include diff --git a/src/qmlwidgets/treetraversalreflector_p.h b/src/qmlwidgets/treetraversalreflector_p.h index bde0f846..39f2443b 100644 --- a/src/qmlwidgets/treetraversalreflector_p.h +++ b/src/qmlwidgets/treetraversalreflector_p.h @@ -1,119 +1,95 @@ /*************************************************************************** * Copyright (C) 2017-2018 by Bluesystems * * 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 . * **************************************************************************/ #pragma once // Qt #include #include // KQuickViews #include struct TreeTraversalItems; class VisualTreeItem; class TreeTraversalReflectorPrivate; class TreeTraversalRange; /** * This class refects a QAbstractItemModel (realtime) topology. * * It helps to handle the various model events to always keep a correct * overview of the model content. While the models are trees, this class expose * a 2D linked list API. This is "better" for the views because in the end they * place widgets in a 2D plane (grid) so geometric navigation makes placing * the widget easier. * * The format this class exposes is not as optimal as it could. However it was * chosen because it made the consumer code more readable and removed most of * the corner cases that causes QtQuick.ListView to fail to be extended to * tables and trees (without exponentially more complexity). It also allows a * nice encapsulation and separation of concern which removes the need for * extensive and non-reusable private APIs. * * Note that you should only use this class directly when implementing low level * views such as charts or graphs. The `AbstractQuickView` is a better base * for most traditional views. */ class TreeTraversalReflector final : public QObject { Q_OBJECT friend class TreeTraversalItems; // Internal representation public: explicit TreeTraversalReflector(QObject* parent = nullptr); virtual ~TreeTraversalReflector(); QAbstractItemModel* model() const; void setModel(QAbstractItemModel* m); void populate(); //TODO move this to the range once it works VisualTreeItem* getCorner(TreeTraversalRange* r, Qt::Corner c) const; // Getter VisualTreeItem* parentTreeItem(const QModelIndex& idx) const; AbstractViewItem* itemForIndex(const QModelIndex& idx) const; + bool isActive(const QModelIndex& parent, int first, int last); //TODO remove those temporary helpers once its encapsulated void refreshEverything(); void reloadRange(const QModelIndex& idx); void moveEverything(); // Mutator bool addRange(TreeTraversalRange* range); bool removeRange(TreeTraversalRange* range); QList ranges() const; // Setters void setItemFactory(std::function factory); // factory AbstractViewItem* createItem() const; - // Tests - void _test_validateTree(TreeTraversalItems* p); - - // Helpers - bool isActive(const QModelIndex& parent, int first, int last); - TreeTraversalItems* addChildren(TreeTraversalItems* parent, const QModelIndex& index); - void bridgeGap(TreeTraversalItems* first, TreeTraversalItems* second, bool insert = false); - void createGap(TreeTraversalItems* first, TreeTraversalItems* last ); - TreeTraversalItems* ttiForIndex(const QModelIndex& idx) const; - - void setTemporaryIndices(const QModelIndex &parent, int start, int end, - const QModelIndex &destination, int row); - void resetTemporaryIndices(const QModelIndex &parent, int start, int end, - const QModelIndex &destination, int row); - -public Q_SLOTS: - void cleanup(); - void slotRowsInserted (const QModelIndex& parent, int first, int last); - void slotRowsRemoved (const QModelIndex& parent, int first, int last); - void slotLayoutChanged ( ); - void slotRowsMoved (const QModelIndex &p, int start, int end, - const QModelIndex &dest, int row); - void slotRowsMoved2 (const QModelIndex &p, int start, int end, - const QModelIndex &dest, int row); - Q_SIGNALS: void contentChanged(); void countChanged (); private: TreeTraversalReflectorPrivate* d_ptr; };