-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
304 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
/* QtNetworkCrumbs - Some networking toys for Qt | ||
* Copyright (C) 2019-2024 Mathias Hasselmann | ||
*/ | ||
#include "detailmodel.h" | ||
|
||
// Qt headers | ||
#include <QUrl> | ||
|
||
namespace qnc::core { | ||
namespace { | ||
|
||
struct Path | ||
{ | ||
static constexpr int BitsPerLength = 2; | ||
static constexpr int BitsPerRow = 10; | ||
static constexpr int MaximumLength = (1 << BitsPerLength) - 1; | ||
static constexpr int MaximumRow = (1 << BitsPerRow) - 1; | ||
static constexpr quintptr LengthMask = static_cast<quintptr>(MaximumLength); | ||
static constexpr quintptr RowMask = static_cast<quintptr>(MaximumRow); | ||
static constexpr quintptr IndexMask = ~LengthMask; | ||
|
||
quintptr value; | ||
|
||
Q_IMPLICIT constexpr Path(quintptr path = 0) noexcept : value{path} {} | ||
Q_IMPLICIT constexpr operator quintptr() const noexcept { return value; } | ||
|
||
explicit constexpr Path(Path parent, int row) noexcept : value{make(parent, row).value} {} | ||
explicit constexpr Path(const QModelIndex &index, int row) noexcept : Path{index.internalId(), row} {} | ||
|
||
[[nodiscard]] constexpr static Path make(Path parent, int row) noexcept | ||
{ | ||
if (row < 0 || row > MaximumRow) | ||
return 0; | ||
if (parent.length() >= MaximumLength) | ||
return 0; | ||
|
||
const auto shift = parent.length() * BitsPerRow + BitsPerLength; | ||
|
||
return (parent.value & IndexMask) | ||
| static_cast<quintptr>(row << shift) | ||
| static_cast<quintptr>(parent.length() + 1); | ||
} | ||
|
||
[[nodiscard]] constexpr int length() const noexcept | ||
{ | ||
return static_cast<int>(value & LengthMask); | ||
} | ||
|
||
[[nodiscard]] constexpr int at(int index) const noexcept | ||
{ | ||
if (Q_UNLIKELY(index < 0 || index >= length())) | ||
return -1; | ||
|
||
const auto shift = index * BitsPerRow + BitsPerLength; | ||
return static_cast<int>((value >> shift) & RowMask); | ||
} | ||
|
||
[[nodiscard]] constexpr int last() const noexcept | ||
{ | ||
return at(length() - 1); | ||
} | ||
|
||
[[nodiscard]] constexpr Path parent() const noexcept | ||
{ | ||
if (Q_UNLIKELY(length() < 1)) | ||
return {}; | ||
|
||
const auto shift = (length() - 1) * BitsPerRow + BitsPerLength; | ||
const auto prefix = (value & ~(RowMask << shift)) & IndexMask; | ||
return prefix | static_cast<quintptr>(length() - 1); | ||
} | ||
|
||
[[nodiscard]] friend constexpr bool operator==(const Path &l, const Path &r) noexcept { return l.value == r.value; } | ||
}; | ||
|
||
static_assert(Path{} .length() == 0); | ||
static_assert(Path{0x00}.length() == 0); | ||
static_assert(Path{0x01}.length() == 1); | ||
static_assert(Path{0x11}.length() == 1); | ||
static_assert(Path{0x13}.length() == 3); | ||
|
||
static_assert(Path{} .at(0) == -1); | ||
static_assert(Path{0x0011}.at(0) == 4); | ||
static_assert(Path{0x0012}.at(1) == 0); | ||
|
||
static_assert(Path{0x841602f}.length() == 3); | ||
static_assert(Path{0x841602f}.at(0) == 11); | ||
static_assert(Path{0x841602f}.at(1) == 22); | ||
static_assert(Path{0x841602f}.at(2) == 33); | ||
static_assert(Path{0x841602f}.at(3) == -1); | ||
|
||
static_assert(Path{0x841602f}.parent().length() == 2); | ||
static_assert(Path{0x841602f}.parent().at(0) == 11); | ||
static_assert(Path{0x841602f}.parent().at(1) == 22); | ||
static_assert(Path{0x841602f}.parent().at(2) == -1); | ||
|
||
static_assert(Path{0x1602e}.length() == 2); | ||
static_assert(Path{0x1602e}.at(0) == 11); | ||
static_assert(Path{0x1602e}.at(1) == 22); | ||
static_assert(Path{0x1602e}.at(2) == -1); | ||
|
||
static_assert(Path{0x1602e, 33}.length() == 3); | ||
static_assert(Path{0x1602e, 33}.at(0) == 11); | ||
static_assert(Path{0x1602e, 33}.at(1) == 22); | ||
static_assert(Path{0x1602e, 33}.at(2) == 33); | ||
static_assert(Path{0x1602e, 33}.at(3) == -1); | ||
|
||
static_assert(Path{0x841602f}.parent() == Path{0x1602e}); | ||
static_assert(Path{0x841602f} == Path{0x1602e, 33}); | ||
|
||
template <DetailModel::Column column, DetailModel::Role role> | ||
[[nodiscard]] QVariant modelData(const QModelIndex &index) | ||
{ | ||
return index.siblingAtColumn(static_cast<int>(column)).data(static_cast<int>(role)); | ||
} | ||
|
||
} // namespace | ||
|
||
void DetailModel::reset(const RowList &rows) | ||
{ | ||
beginResetModel(); | ||
m_rows = rows; | ||
endResetModel(); | ||
} | ||
|
||
QModelIndex DetailModel::index(int row, int column, const QModelIndex &parent) const | ||
{ | ||
if (parent.isValid()) | ||
return createIndex(row, column, Path{parent, parent.row()}); | ||
|
||
return createIndex(row, column, Path{}); | ||
} | ||
|
||
QModelIndex DetailModel::parent(const QModelIndex &child) const | ||
{ | ||
if (const auto path = Path{child.internalId()}; path.length() > 0) | ||
return createIndex(path.last(), 0, path.parent()); | ||
|
||
return {}; | ||
} | ||
|
||
int DetailModel::rowCount(const QModelIndex &parent) const | ||
{ | ||
if (!parent.isValid()) | ||
return static_cast<int>(m_rows.size()); | ||
else if (const auto &data = value(parent); data.canConvert<RowList>()) | ||
return static_cast<int>(qvariant_cast<RowList>(data).size()); | ||
else | ||
return 0; | ||
} | ||
|
||
int DetailModel::columnCount(const QModelIndex &) const | ||
{ | ||
return 2; | ||
} | ||
|
||
QVariant DetailModel::Row::data(Column column, Role role) const | ||
{ | ||
switch (static_cast<Role>(role)) { | ||
case Role::Display: | ||
return displayData(column); | ||
case Role::Value: | ||
return valueData(column); | ||
} | ||
|
||
return {}; | ||
} | ||
|
||
QVariant DetailModel::Row::displayData(Column column) const | ||
{ | ||
switch (column) { | ||
case Column::Name: | ||
return name; | ||
case Column::Value: | ||
return value.toString(); | ||
} | ||
|
||
return {}; | ||
} | ||
|
||
QVariant DetailModel::Row::valueData(Column column) const | ||
{ | ||
switch (column) { | ||
case Column::Name: | ||
return name; | ||
case Column::Value: | ||
return value; | ||
} | ||
|
||
return {}; | ||
} | ||
|
||
QVariant DetailModel::data(const QModelIndex &index, int role) const | ||
{ | ||
if (checkIndex(index, CheckIndexOption::IndexIsValid)) { | ||
const auto path = Path{index.internalId()}; | ||
auto rowList = m_rows; | ||
|
||
for (auto i = 0; i < path.length(); ++i) { | ||
const auto &row = rowList.at(path.at(i)); | ||
Q_ASSERT(row.value.canConvert<RowList>()); | ||
rowList = qvariant_cast<RowList>(row.value); | ||
} | ||
|
||
const auto &row = rowList.at(index.row()); | ||
const auto column = static_cast<Column>(index.column()); | ||
return row.data(column, static_cast<Role>(role)); | ||
} | ||
|
||
return {}; | ||
} | ||
|
||
QVariant DetailModel::headerData(int section, Qt::Orientation orientation, int role) const | ||
{ | ||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { | ||
switch (static_cast<Column>(section)) { | ||
case Column::Name: | ||
return tr("Property"); | ||
case Column::Value: | ||
return tr("Value"); | ||
} | ||
} | ||
|
||
return {}; | ||
} | ||
|
||
QVariant DetailModel::value(const QModelIndex &index) | ||
{ | ||
return modelData<Column::Value, Role::Value>(index); | ||
} | ||
|
||
QUrl DetailModel::url(const QModelIndex &index) | ||
{ | ||
if (const auto &data = value(index); data.userType() == QMetaType::QUrl) | ||
return data.toUrl(); | ||
|
||
return {}; | ||
} | ||
|
||
} // namespace qnc::core |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* QtNetworkCrumbs - Some networking toys for Qt | ||
* Copyright (C) 2019-2024 Mathias Hasselmann | ||
*/ | ||
#ifndef QNCCORE_DETAILMODEL_H | ||
#define QNCCORE_DETAILMODEL_H | ||
|
||
// Qt headers | ||
#include <QAbstractItemModel> | ||
|
||
namespace qnc::core { | ||
|
||
class DetailModel : public QAbstractItemModel | ||
{ | ||
Q_OBJECT | ||
|
||
public: | ||
enum class Column | ||
{ | ||
Name, | ||
Value, | ||
}; | ||
|
||
enum class Role | ||
{ | ||
Display = Qt::DisplayRole, | ||
Value = Qt::UserRole + 1024, | ||
}; | ||
|
||
struct Row | ||
{ | ||
QString name; | ||
QVariant value; | ||
|
||
[[nodiscard]] QVariant data(Column column, Role role) const; | ||
[[nodiscard]] QVariant displayData(Column column) const; | ||
[[nodiscard]] QVariant valueData(Column column) const; | ||
}; | ||
|
||
using RowList = QList<Row>; | ||
|
||
using QAbstractItemModel::QAbstractItemModel; | ||
|
||
void reset(const RowList &rows); | ||
|
||
static QVariant value(const QModelIndex &index); | ||
static QUrl url(const QModelIndex &index); | ||
|
||
public: // QAbstractItemModel interface | ||
QModelIndex index(int row, int column, const QModelIndex &parent) const override; | ||
QModelIndex parent(const QModelIndex &child) const override; | ||
int rowCount(const QModelIndex &parent) const override; | ||
int columnCount(const QModelIndex &parent) const override; | ||
QVariant data(const QModelIndex &index, int role) const override; | ||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override; | ||
|
||
private: | ||
RowList m_rows; | ||
}; | ||
|
||
} // namespace qnc::core | ||
|
||
#endif // QNCCORE_DETAILMODEL_H |