Loading...
Searching...
No Matches
TreeNodeItemModel.hpp
1#pragma once
2#include <QAbstractItemModel>
3
4#include <score_lib_base_export.h>
5
12// TESTME
13class TreePath;
14class SCORE_LIB_BASE_EXPORT TreeModel : public QAbstractItemModel
15{
16public:
17 using QAbstractItemModel::QAbstractItemModel;
19 template <typename F>
20 void iterate(const QModelIndex& idx, const F& f)
21 {
22 if(idx.isValid())
23 f(idx);
24
25 if(!hasChildren(idx))
26 return;
27
28 const int rows = rowCount(idx);
29 for(int i = 0; i < rows; ++i)
30 iterate(this->index(i, 0, idx), f);
31 }
32
33 QModelIndex convertPathToIndex(const TreePath& path) const;
34};
35
36template <typename NodeType>
38{
39public:
40 explicit TreeNodeBasedItemModel(QObject* parent = nullptr)
41 : TreeModel{parent}
42 {
43 // The row caches map rows to child nodes; any structural change may
44 // shift rows or erase cached elements. These connections are made
45 // before any observer can connect, so the caches die before an
46 // observer gets to query the post-mutation model.
47 auto invalidate = [this] {
48 m_rowCache[0].parent = nullptr;
49 m_rowCache[1].parent = nullptr;
50 };
51 connect(this, &QAbstractItemModel::rowsInserted, this, invalidate);
52 connect(this, &QAbstractItemModel::rowsRemoved, this, invalidate);
53 connect(this, &QAbstractItemModel::rowsMoved, this, invalidate);
54 connect(this, &QAbstractItemModel::modelReset, this, invalidate);
55 connect(this, &QAbstractItemModel::layoutChanged, this, invalidate);
56 }
57
58 using node_type = NodeType;
59 virtual ~TreeNodeBasedItemModel() = default;
60 virtual NodeType& rootNode() = 0;
61 virtual const NodeType& rootNode() const = 0;
62
63 NodeType& nodeFromModelIndex(const QModelIndex& index) const
64 {
65 auto n = index.isValid() ? static_cast<NodeType*>(index.internalPointer())
66 : const_cast<NodeType*>(&rootNode());
67
68 SCORE_ASSERT(n);
69 return *n;
70 }
71
72 QModelIndex parent(const QModelIndex& index) const final override
73 {
74 if(!index.isValid())
75 return QModelIndex();
76 if(index.model() != this)
77 return QModelIndex();
78
79 const auto& node = nodeFromModelIndex(index);
80 auto parentNode = node.parent();
81
82 if(!parentNode)
83 return QModelIndex();
84
85 auto grandparentNode = parentNode->parent();
86
87 if(!grandparentNode)
88 return QModelIndex();
89
90 const int rowParent = grandparentNode->indexOfChild(parentNode);
91 if(rowParent == -1)
92 return QModelIndex();
93
94 return createIndex(rowParent, 0, parentNode);
95 }
96
97 QModelIndex index(int row, int column, const QModelIndex& parent) const final override
98 {
99 if(!hasIndex(row, column, parent))
100 return QModelIndex();
101
102 auto& parentItem = nodeFromModelIndex(parent);
103 if(!parentItem.hasChild(row))
104 return QModelIndex();
105
106 // Children live in a std::list: childAt(row) is O(row), which makes any
107 // consumer that resolves many rows of one parent - a view painting, a
108 // QSortFilterProxyModel building a mapping, persistent-index updates
109 // after an insert - O(n²) overall. Cache the row → node table for the
110 // two most recently used parents (two, so a parent/child walk does not
111 // thrash); structural changes invalidate through the signal connections
112 // made in the constructor.
113 auto* cache = &m_rowCache[0];
114 if(m_rowCache[0].parent != &parentItem)
115 {
116 if(m_rowCache[1].parent == &parentItem)
117 {
118 cache = &m_rowCache[1];
119 }
120 else
121 {
122 // Replace the least-recently-used slot
123 cache = (m_lastUsed == 0) ? &m_rowCache[1] : &m_rowCache[0];
124 cache->parent = &parentItem;
125 cache->rows.clear();
126 cache->rows.reserve(parentItem.childCount());
127 for(auto& child : parentItem)
128 cache->rows.push_back(&child);
129 }
130 }
131 m_lastUsed = int(cache - &m_rowCache[0]);
132 return createIndex(row, column, cache->rows[row]);
133 }
134
135 int rowCount(const QModelIndex& parent) const final override
136 {
137 if(parent.column() > 0)
138 return 0;
139
140 const auto& parentNode = nodeFromModelIndex(parent);
141 return parentNode.childCount();
142 }
143
144 bool hasChildren(const QModelIndex& parent) const final override
145 {
146 const auto& parentNode = nodeFromModelIndex(parent);
147 return parentNode.childCount() > 0;
148 }
149
150private:
151 struct RowCache
152 {
153 NodeType* parent{};
154 std::vector<NodeType*> rows;
155 };
156 mutable RowCache m_rowCache[2];
157 mutable int m_lastUsed{};
158};
Definition TreeNodeItemModel.hpp:15
void iterate(const QModelIndex &idx, const F &f)
idx: should be the root index of the view
Definition TreeNodeItemModel.hpp:20
Definition TreeNodeItemModel.hpp:38
Path in a tree of QAbstractItemModel objects.
Definition TreePath.hpp:34