Skip to content

Commit 33129f3

Browse files
authored
Merge pull request #33 from hasselmm/feature/generic-models
Add some generic item models
2 parents b8413f5 + d1c3460 commit 33129f3

7 files changed

+1140
-0
lines changed

core/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ qnc_add_library(
44
abstractresolver.cpp
55
abstractresolver.h
66
compat.h
7+
detailmodel.cpp
8+
detailmodel.h
79
literals.cpp
810
literals.h
911
multicastresolver.cpp
1012
multicastresolver.h
1113
parse.cpp
1214
parse.h
15+
treemodel.cpp
16+
treemodel.h
1317
)
1418

1519
target_link_libraries(QncCore PUBLIC Qt::Network)

core/detailmodel.cpp

+303
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
/* QtNetworkCrumbs - Some networking toys for Qt
2+
* Copyright (C) 2019-2024 Mathias Hasselmann
3+
*/
4+
#include "detailmodel.h"
5+
6+
// Qt headers
7+
#include <QLoggingCategory>
8+
#include <QUrl>
9+
10+
namespace qnc::core {
11+
namespace {
12+
13+
Q_LOGGING_CATEGORY(lcDetailModel, "qnc.core.detailmodel")
14+
15+
struct Path
16+
{
17+
static constexpr int BitsPerLength = 2;
18+
static constexpr int BitsPerRow = 10;
19+
static constexpr int MaximumLength = (1 << BitsPerLength) - 1;
20+
static constexpr int MaximumRow = (1 << BitsPerRow) - 1;
21+
static constexpr quintptr LengthMask = static_cast<quintptr>(MaximumLength);
22+
static constexpr quintptr RowMask = static_cast<quintptr>(MaximumRow);
23+
static constexpr quintptr IndexMask = ~LengthMask;
24+
25+
quintptr value;
26+
27+
Q_IMPLICIT constexpr Path(quintptr path = 0) noexcept : value{path} {}
28+
Q_IMPLICIT constexpr operator quintptr() const noexcept { return value; }
29+
30+
explicit constexpr Path(const QModelIndex &index) noexcept : Path{index.internalId()} {}
31+
explicit constexpr Path(Path parent, int row) noexcept : value{make(parent, row).value} {}
32+
explicit constexpr Path(const QModelIndex &parent, int row) noexcept : Path{parent.internalId(), row} {}
33+
34+
[[nodiscard]] constexpr static Path make(Path parent, int row) noexcept
35+
{
36+
if (row < 0 || row > MaximumRow)
37+
return 0;
38+
if (parent.length() >= MaximumLength)
39+
return 0;
40+
41+
const auto shift = parent.length() * BitsPerRow + BitsPerLength;
42+
43+
return (parent.value & IndexMask)
44+
| static_cast<quintptr>(row << shift)
45+
| static_cast<quintptr>(parent.length() + 1);
46+
}
47+
48+
[[nodiscard]] constexpr int length() const noexcept
49+
{
50+
return static_cast<int>(value & LengthMask);
51+
}
52+
53+
[[nodiscard]] constexpr int at(int index) const noexcept
54+
{
55+
if (Q_UNLIKELY(index < 0 || index >= length()))
56+
return -1;
57+
58+
const auto shift = index * BitsPerRow + BitsPerLength;
59+
return static_cast<int>((value >> shift) & RowMask);
60+
}
61+
62+
[[nodiscard]] constexpr int last() const noexcept
63+
{
64+
return at(length() - 1);
65+
}
66+
67+
[[nodiscard]] constexpr Path parent() const noexcept
68+
{
69+
if (Q_UNLIKELY(length() < 1))
70+
return {};
71+
72+
const auto shift = (length() - 1) * BitsPerRow + BitsPerLength;
73+
const auto prefix = (value & ~(RowMask << shift)) & IndexMask;
74+
return prefix | static_cast<quintptr>(length() - 1);
75+
}
76+
77+
[[nodiscard]] friend constexpr bool operator==(const Path &l, const Path &r) noexcept { return l.value == r.value; }
78+
};
79+
80+
static_assert(Path{} .length() == 0);
81+
static_assert(Path{0x00}.length() == 0);
82+
static_assert(Path{0x01}.length() == 1);
83+
static_assert(Path{0x11}.length() == 1);
84+
static_assert(Path{0x13}.length() == 3);
85+
86+
static_assert(Path{} .at(0) == -1);
87+
static_assert(Path{0x0011}.at(0) == 4);
88+
static_assert(Path{0x0012}.at(1) == 0);
89+
90+
static_assert(Path{0x841602f}.length() == 3);
91+
static_assert(Path{0x841602f}.at(0) == 11);
92+
static_assert(Path{0x841602f}.at(1) == 22);
93+
static_assert(Path{0x841602f}.at(2) == 33);
94+
static_assert(Path{0x841602f}.at(3) == -1);
95+
96+
static_assert(Path{0x841602f}.parent().length() == 2);
97+
static_assert(Path{0x841602f}.parent().at(0) == 11);
98+
static_assert(Path{0x841602f}.parent().at(1) == 22);
99+
static_assert(Path{0x841602f}.parent().at(2) == -1);
100+
101+
static_assert(Path{0x1602e}.length() == 2);
102+
static_assert(Path{0x1602e}.at(0) == 11);
103+
static_assert(Path{0x1602e}.at(1) == 22);
104+
static_assert(Path{0x1602e}.at(2) == -1);
105+
106+
static_assert(Path{0x1602e, 33}.length() == 3);
107+
static_assert(Path{0x1602e, 33}.at(0) == 11);
108+
static_assert(Path{0x1602e, 33}.at(1) == 22);
109+
static_assert(Path{0x1602e, 33}.at(2) == 33);
110+
static_assert(Path{0x1602e, 33}.at(3) == -1);
111+
112+
static_assert(Path{0x841602f}.parent() == Path{0x1602e});
113+
static_assert(Path{0x841602f} == Path{0x1602e, 33});
114+
115+
template <DetailModel::Column column, DetailModel::Role role>
116+
[[nodiscard]] QVariant modelData(const QModelIndex &index)
117+
{
118+
const auto &sibling = index.siblingAtColumn(qToUnderlying(column));
119+
return sibling.data(qToUnderlying(role));
120+
}
121+
122+
QByteArray toByteArray(Path path)
123+
{
124+
auto string = QByteArray{};
125+
126+
if (path.length() > 0) {
127+
string += QByteArray::number(path.at(0));
128+
129+
for (auto i = 1; i < path.length(); ++i) {
130+
string += '/';
131+
string += QByteArray::number(path.at(i));
132+
}
133+
}
134+
135+
return string;
136+
}
137+
138+
bool validate(const DetailModel::RowList &rows, Path path = {})
139+
{
140+
auto valid = true;
141+
142+
if (rows.count() > Path::MaximumRow) {
143+
qCWarning(lcDetailModel, "Too many items at (%s); ignoring rows from %d to %d",
144+
toByteArray(path).constData(), Path::MaximumRow + 1,
145+
static_cast<int>(rows.count()));
146+
147+
valid = false;
148+
}
149+
150+
for (auto i = 0; i < rows.count(); ++i) {
151+
if (rows[i].hasChildren()) {
152+
if (path.length() == Path::MaximumLength) {
153+
qCWarning(lcDetailModel,
154+
"Maximum tree depth reached; ignoring children of (%s/%d)",
155+
toByteArray(path).constData(), i);
156+
157+
valid = false;
158+
} else {
159+
valid &= validate(rows[i].children(), Path{path, i});
160+
}
161+
}
162+
}
163+
164+
return valid;
165+
}
166+
167+
} // namespace
168+
169+
void DetailModel::reset(const RowList &rows)
170+
{
171+
core::validate(rows);
172+
173+
beginResetModel();
174+
m_rows = rows;
175+
endResetModel();
176+
}
177+
178+
QModelIndex DetailModel::index(int row, int column, const QModelIndex &parent) const
179+
{
180+
if (!parent.isValid())
181+
return createIndex(row, column, Path{});
182+
else if (Path{parent}.length() < Path::MaximumLength)
183+
return createIndex(row, column, Path{parent, parent.row()});
184+
else
185+
return {};
186+
}
187+
188+
QModelIndex DetailModel::parent(const QModelIndex &child) const
189+
{
190+
if (const auto path = Path{child.internalId()}; path.length() > 0)
191+
return createIndex(path.last(), 0, path.parent());
192+
193+
return {};
194+
}
195+
196+
int DetailModel::rowCount(const QModelIndex &parent) const
197+
{
198+
if (!parent.isValid())
199+
return std::min(static_cast<int>(m_rows.size()), Path::MaximumRow);
200+
else if (parent.column() != 0)
201+
return 0;
202+
else if (Path{parent}.length() == Path::MaximumLength)
203+
return 0;
204+
else if (const auto &data = value(parent); !hasChildren(data))
205+
return 0;
206+
else
207+
return std::min(static_cast<int>(children(data).size()), Path::MaximumRow);
208+
}
209+
210+
int DetailModel::columnCount(const QModelIndex &) const
211+
{
212+
return 2;
213+
}
214+
215+
QVariant DetailModel::Row::data(Column column, Role role) const
216+
{
217+
switch (static_cast<Role>(role)) {
218+
case Role::Display:
219+
return displayData(column);
220+
case Role::Value:
221+
return valueData(column);
222+
}
223+
224+
return {};
225+
}
226+
227+
QVariant DetailModel::Row::displayData(Column column) const
228+
{
229+
switch (column) {
230+
case Column::Name:
231+
return name;
232+
case Column::Value:
233+
return value.toString();
234+
}
235+
236+
return {};
237+
}
238+
239+
QVariant DetailModel::Row::valueData(Column column) const
240+
{
241+
switch (column) {
242+
case Column::Name:
243+
return name;
244+
case Column::Value:
245+
return value;
246+
}
247+
248+
return {};
249+
}
250+
251+
QVariant DetailModel::data(const QModelIndex &index, int role) const
252+
{
253+
if (index.isValid() && checkIndex(index)) {
254+
const auto path = Path{index.internalId()};
255+
auto rowList = m_rows;
256+
257+
for (auto i = 0; i < path.length(); ++i) {
258+
const auto &row = rowList.at(path.at(i));
259+
Q_ASSERT(row.hasChildren());
260+
rowList = row.children();
261+
}
262+
263+
const auto &row = rowList.at(index.row());
264+
const auto column = static_cast<Column>(index.column());
265+
return row.data(column, static_cast<Role>(role));
266+
}
267+
268+
return {};
269+
}
270+
271+
QVariant DetailModel::headerData(int section, Qt::Orientation orientation, int role) const
272+
{
273+
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
274+
switch (static_cast<Column>(section)) {
275+
case Column::Name:
276+
return tr("Property");
277+
case Column::Value:
278+
return tr("Value");
279+
}
280+
}
281+
282+
return {};
283+
}
284+
285+
QVariant DetailModel::value(const QModelIndex &index)
286+
{
287+
return modelData<Column::Value, Role::Value>(index);
288+
}
289+
290+
QUrl DetailModel::url(const QModelIndex &index)
291+
{
292+
if (const auto &data = value(index); data.userType() == QMetaType::QUrl)
293+
return data.toUrl();
294+
295+
return {};
296+
}
297+
298+
bool DetailModel::validate(const RowList &rows)
299+
{
300+
return core::validate(rows);
301+
}
302+
303+
} // namespace qnc::core

0 commit comments

Comments
 (0)