2025-08-08 07:05:55 +00:00
|
|
|
|
// Copyright (C) 2025 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
|
|
|
|
|
|
|
|
|
#include <QtCore>
|
|
|
|
|
#include <QtWidgets>
|
|
|
|
|
|
|
|
|
|
#if __has_include(<QtQml>)
|
|
|
|
|
#define QUICK_UI
|
|
|
|
|
#include <QtQml/QQmlApplicationEngine>
|
|
|
|
|
#include <QtQml/QQmlContext>
|
2025-08-22 12:17:31 +00:00
|
|
|
|
#include <QQuickWidget>
|
|
|
|
|
#include <QQuickItem>
|
2025-08-27 14:31:46 +00:00
|
|
|
|
#elif defined(Q_CC_MSVC)
|
|
|
|
|
#pragma message "Building without Quick UI"
|
2025-08-08 07:05:55 +00:00
|
|
|
|
#else
|
|
|
|
|
#warning "Building without Quick UI"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <list>
|
|
|
|
|
#include <ranges>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
|
|
|
|
|
|
class Gadget
|
|
|
|
|
{
|
|
|
|
|
Q_GADGET
|
|
|
|
|
Q_PROPERTY(QString display READ display WRITE setDisplay)
|
|
|
|
|
Q_PROPERTY(QColor decoration READ decoration WRITE setDecoration)
|
|
|
|
|
Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip)
|
|
|
|
|
public:
|
|
|
|
|
Gadget() = default;
|
|
|
|
|
|
|
|
|
|
Gadget(const QString &display, QColor decoration, const QString &toolTip)
|
|
|
|
|
: m_display(display), m_decoration(decoration), m_toolTip(toolTip)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString display() const { return m_display; }
|
|
|
|
|
void setDisplay(const QString &display) { m_display = display; }
|
|
|
|
|
QColor decoration() const { return m_decoration; }
|
|
|
|
|
void setDecoration(QColor decoration) { m_decoration = decoration; }
|
|
|
|
|
QString toolTip() const { return m_toolTip.isEmpty() ? m_display : m_toolTip; }
|
|
|
|
|
void setToolTip(const QString &toolTip) { m_toolTip = toolTip; }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QString m_display;
|
|
|
|
|
QColor m_decoration;
|
|
|
|
|
QString m_toolTip;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct QMetaEnumerator
|
|
|
|
|
{
|
|
|
|
|
struct iterator
|
|
|
|
|
{
|
|
|
|
|
using difference_type = int;
|
|
|
|
|
using size_type = int;
|
|
|
|
|
using pointer = void;
|
|
|
|
|
using iterator_category = std::input_iterator_tag;
|
|
|
|
|
using value_type = std::tuple<int, QByteArray, int>;
|
|
|
|
|
using reference = value_type;
|
|
|
|
|
using const_reference = const value_type;
|
|
|
|
|
|
|
|
|
|
friend constexpr iterator &operator++(iterator &that)
|
|
|
|
|
{ ++that.m_index; return that; }
|
|
|
|
|
friend constexpr iterator operator++(iterator &that, int)
|
|
|
|
|
{ auto copy = that; ++that.m_index; return copy; }
|
|
|
|
|
friend constexpr iterator &operator+=(iterator &that, int n)
|
|
|
|
|
{ that.m_index += n; return that; }
|
|
|
|
|
|
|
|
|
|
friend constexpr bool comparesEqual(const iterator &lhs, const iterator &rhs) noexcept
|
|
|
|
|
{
|
|
|
|
|
return lhs.m_index == rhs.m_index && lhs.m_enum == rhs.m_enum;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
friend constexpr Qt::strong_ordering compareThreeWay(const iterator &lhs,
|
|
|
|
|
const iterator &rhs) noexcept
|
|
|
|
|
{
|
|
|
|
|
return Qt::compareThreeWay(lhs.m_index, rhs.m_index);
|
|
|
|
|
}
|
|
|
|
|
Q_DECLARE_STRONGLY_ORDERED(iterator)
|
|
|
|
|
|
|
|
|
|
const_reference operator*() const
|
|
|
|
|
{ return {m_index, m_enum->key(m_index), m_enum->value(m_index)}; }
|
|
|
|
|
|
|
|
|
|
const QMetaEnum *m_enum = nullptr;
|
|
|
|
|
int m_index = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static_assert(std::input_iterator<iterator>);
|
|
|
|
|
|
|
|
|
|
using size_type = iterator::size_type;
|
|
|
|
|
using value_type = iterator::value_type;
|
|
|
|
|
using const_iterator = iterator;
|
|
|
|
|
|
|
|
|
|
template <typename Enum>
|
|
|
|
|
explicit QMetaEnumerator(Enum e) noexcept
|
|
|
|
|
: m_enum(QMetaEnum::fromType<Enum>())
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
const_iterator begin() const { return iterator{&m_enum, 0}; }
|
|
|
|
|
const_iterator end() const { return iterator{&m_enum, size()}; }
|
|
|
|
|
size_type size() const { return m_enum.keyCount(); }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
const QMetaEnum m_enum;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct TreeRow;
|
|
|
|
|
using Tree = std::vector<TreeRow>;
|
|
|
|
|
|
|
|
|
|
struct TreeRow
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
TreeRow() = default;
|
|
|
|
|
|
|
|
|
|
TreeRow(const QString &name, const QString &title)
|
|
|
|
|
: m_name(name), m_title(title)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
~TreeRow() = default;
|
|
|
|
|
TreeRow(TreeRow &&other) = default;
|
|
|
|
|
TreeRow &operator=(TreeRow &&other) = default;
|
|
|
|
|
|
|
|
|
|
template <typename ...Args>
|
|
|
|
|
TreeRow &addChild(Args&& ...args)
|
|
|
|
|
{
|
|
|
|
|
if (!m_children)
|
|
|
|
|
m_children.emplace(Tree{});
|
|
|
|
|
TreeRow &res = m_children->emplace_back(args...);
|
|
|
|
|
res.m_parent = this;
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// tree traversal protocol implementation
|
|
|
|
|
const TreeRow *parentRow() const { return m_parent; }
|
|
|
|
|
void setParentRow(TreeRow *parent) { m_parent = parent; }
|
|
|
|
|
const std::optional<Tree> &childRows() const { return m_children; }
|
|
|
|
|
std::optional<Tree> &childRows() { return m_children; }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QString m_name;
|
|
|
|
|
QString m_title;
|
|
|
|
|
|
|
|
|
|
TreeRow *m_parent = nullptr;
|
|
|
|
|
std::optional<Tree> m_children = std::nullopt;
|
|
|
|
|
|
|
|
|
|
template<size_t I, typename Row,
|
|
|
|
|
std::enable_if_t<std::is_same_v<q20::remove_cvref_t<Row>, TreeRow>, bool> = true>
|
|
|
|
|
friend inline decltype(auto) get(Row &&row)
|
|
|
|
|
{
|
|
|
|
|
if constexpr (I == 0)
|
|
|
|
|
return q23::forward_like<Row>(row.m_name);
|
|
|
|
|
else if constexpr (I == 1)
|
|
|
|
|
return q23::forward_like<Row>(row.m_title);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
namespace std {
|
|
|
|
|
template <> struct tuple_size<TreeRow> : std::integral_constant<size_t, 2> {};
|
|
|
|
|
template <size_t I> struct tuple_element<I, TreeRow>
|
|
|
|
|
{ using type = decltype(get<I>(std::declval<TreeRow>())); };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Object : public QObject
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
Q_PROPERTY(QString display READ display WRITE setDisplay NOTIFY displayChanged)
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
Object(int x)
|
|
|
|
|
: m_display(QString::number(x))
|
|
|
|
|
{}
|
|
|
|
|
QString display() const { return m_display; }
|
|
|
|
|
void setDisplay(const QString &d) { m_display = d; displayChanged(); }
|
|
|
|
|
|
|
|
|
|
Q_SIGNALS:
|
|
|
|
|
void displayChanged();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QString m_display;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template <>
|
|
|
|
|
struct QRangeModel::RowOptions<Object>
|
|
|
|
|
{
|
|
|
|
|
static constexpr auto rowCategory = QRangeModel::RowCategory::MultiRoleItem;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class ModelFactory : public QObject
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
std::vector<int> numbers = {1, 2, 3, 4, 5};
|
|
|
|
|
QList<QString> strings = {u"one"_s, u"two"_s, u"three"_s};
|
2025-08-21 14:31:30 +00:00
|
|
|
|
std::array<int, 1000000> largeArray = {};
|
2025-08-08 07:05:55 +00:00
|
|
|
|
|
|
|
|
|
public slots:
|
|
|
|
|
QRangeModel *makeNumbers()
|
|
|
|
|
{
|
|
|
|
|
return new QRangeModel(&numbers);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-21 14:31:30 +00:00
|
|
|
|
QRangeModel *makeLargeArray()
|
|
|
|
|
{
|
|
|
|
|
return new QRangeModel(&largeArray);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-08-08 07:05:55 +00:00
|
|
|
|
QRangeModel *makeStrings()
|
|
|
|
|
{
|
|
|
|
|
return new QRangeModel(std::ref(strings));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRangeModel *makeJson()
|
|
|
|
|
{
|
|
|
|
|
QJsonDocument json = QJsonDocument::fromJson(R"(
|
|
|
|
|
[ "one", "two", 12345 ]
|
|
|
|
|
)");
|
|
|
|
|
Q_ASSERT(json.isArray());
|
|
|
|
|
return new QRangeModel(json.array());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRangeModel *makeListOfTuples()
|
|
|
|
|
{
|
|
|
|
|
std::list<std::tuple<int, QString>> data = {
|
|
|
|
|
{ 1, "eins"},
|
|
|
|
|
{ 2, "zwei"},
|
|
|
|
|
{ 3, "drei"},
|
|
|
|
|
{ 4, "vier"},
|
|
|
|
|
{ 5, "fünf"},
|
|
|
|
|
};
|
|
|
|
|
return new QRangeModel(data);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 19:14:50 +00:00
|
|
|
|
QRangeModel *makeListOfArrays()
|
|
|
|
|
{
|
|
|
|
|
QList<std::array<double, 2000>> data = {
|
|
|
|
|
{0.0},
|
|
|
|
|
{1.1},
|
|
|
|
|
{2.2},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return new QRangeModel(std::move(data));
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 07:05:55 +00:00
|
|
|
|
QRangeModel *makeCustomFromEnum()
|
|
|
|
|
{
|
|
|
|
|
return new QRangeModel(QMetaEnumerator(Qt::ItemDataRole{}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRangeModel *makeBoundedIota()
|
|
|
|
|
{
|
|
|
|
|
return new QRangeModel(std::views::iota(1, 10000));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRangeModel *makeUnboundedIota()
|
|
|
|
|
{
|
|
|
|
|
auto view = std::views::iota(1);
|
|
|
|
|
return new QRangeModel(view);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRangeModel *makeZipView()
|
|
|
|
|
{
|
|
|
|
|
static auto x = QList<int>{1, 2, 3, 4, 5};
|
|
|
|
|
static auto y = std::list<QString>{"α", "β", "γ", "δ", "ε"};
|
|
|
|
|
static auto z = std::array<QChar, 6>{u'A', u'B', u'C', u'D', u'E', u'F'};
|
|
|
|
|
|
|
|
|
|
return new QRangeModel(std::views::zip(x, y, z));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRangeModel *makeGadgetTable()
|
|
|
|
|
{
|
|
|
|
|
QList<QList<Gadget>> gadgetTable = {
|
|
|
|
|
{{"1/1", Qt::red, "red"}, {"1/2", Qt::black, "black"}},
|
|
|
|
|
{{"2/1", Qt::blue, "blue"}, {"2/2", Qt::green, "green"}},
|
|
|
|
|
};
|
|
|
|
|
return new QRangeModel(gadgetTable);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 11:31:13 +00:00
|
|
|
|
QRangeModel *makeQtMap()
|
|
|
|
|
{
|
|
|
|
|
return new QRangeModel(QMap<QString, QString>{
|
|
|
|
|
{"one", "eins"},
|
|
|
|
|
{"two", "zwei"},
|
|
|
|
|
{"three", "drei"},
|
|
|
|
|
{"four", "vier"},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRangeModel *makeStdMap()
|
|
|
|
|
{
|
|
|
|
|
return new QRangeModel(std::map<int, double>{
|
|
|
|
|
{1, 0.1},
|
|
|
|
|
{2, 0.2},
|
|
|
|
|
{3, 0.3},
|
|
|
|
|
{4, 0.4},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-29 13:24:44 +00:00
|
|
|
|
QRangeModel *makeFilterView()
|
|
|
|
|
{
|
|
|
|
|
auto view = std::views::iota(0, 100)
|
|
|
|
|
| std::views::filter([](int i){ return 0 == i % 2; })
|
|
|
|
|
| std::views::transform([](int i){ return i * i; });
|
|
|
|
|
|
2025-08-30 09:52:04 +00:00
|
|
|
|
return new QRangeModel(view);
|
2025-08-29 13:24:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 07:05:55 +00:00
|
|
|
|
QRangeModel *makeMultiRoleMap()
|
|
|
|
|
{
|
|
|
|
|
using ColorEntry = QMap<Qt::ItemDataRole, QVariant>;
|
|
|
|
|
|
|
|
|
|
const QStringList colorNames = QColor::colorNames();
|
|
|
|
|
QList<ColorEntry> colors;
|
|
|
|
|
colors.reserve(colorNames.size());
|
|
|
|
|
for (const QString &name : QColor::colorNames()) {
|
|
|
|
|
const QColor color = QColor::fromString(name);
|
|
|
|
|
colors << ColorEntry{{Qt::DisplayRole, name},
|
|
|
|
|
{Qt::DecorationRole, color},
|
|
|
|
|
{Qt::ToolTipRole, color.name()}};
|
|
|
|
|
}
|
|
|
|
|
return new QRangeModel(colors);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRangeModel *makeUniqueObjects()
|
|
|
|
|
{
|
|
|
|
|
std::array<std::unique_ptr<Object>, 3> data = {
|
|
|
|
|
std::make_unique<Object>(1),
|
|
|
|
|
std::make_unique<Object>(2),
|
|
|
|
|
std::make_unique<Object>(3),
|
|
|
|
|
};
|
|
|
|
|
return new QRangeModel(std::move(data));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRangeModel *makeUniquePtrArray()
|
|
|
|
|
{
|
|
|
|
|
// not possible, values need to be copyable
|
|
|
|
|
// std::array<std::unique_ptr<QString>, 3> data = {
|
|
|
|
|
// std::make_unique<QString>("A"),
|
|
|
|
|
// std::make_unique<QString>("B"),
|
|
|
|
|
// std::make_unique<QString>("C"),
|
|
|
|
|
// };
|
|
|
|
|
// return new QRangeModel(std::move(data));
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRangeModel *makeUniqueRows()
|
|
|
|
|
{
|
|
|
|
|
std::array<std::unique_ptr<std::vector<QString>>, 3> data = {
|
|
|
|
|
std::make_unique<std::vector<QString>>(std::vector<QString>{u"A"_s, u"B"_s, u"C"_s}),
|
|
|
|
|
std::make_unique<std::vector<QString>>(std::vector<QString>{u"D"_s, u"E"_s, u"F"_s}),
|
|
|
|
|
std::make_unique<std::vector<QString>>(std::vector<QString>{u"G"_s, u"H"_s, u"I"_s}),
|
|
|
|
|
};
|
|
|
|
|
return new QRangeModel(std::move(data));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRangeModel *makeTree()
|
|
|
|
|
{
|
|
|
|
|
static TreeRow root[] = {{"Germany", "Berlin"},
|
|
|
|
|
{"France", "Paris"},
|
|
|
|
|
{"Austria", "Vienna"}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static Tree europe{std::make_move_iterator(std::begin(root)), std::make_move_iterator(std::end(root))};
|
|
|
|
|
TreeRow &bavaria = europe[0].addChild("Bavaria", "Munich");
|
|
|
|
|
bavaria.addChild("Upper Bavaria", "München");
|
|
|
|
|
bavaria.addChild("Lower Bavaria", "Landshut");
|
|
|
|
|
bavaria.addChild("Upper Palatinate", "Regensburg");
|
|
|
|
|
bavaria.addChild("Swabia", "Augsburg");
|
|
|
|
|
bavaria.addChild("Franconia", "Nürnberg");
|
|
|
|
|
bavaria.addChild("Upper Franconia", "Bayreuth");
|
|
|
|
|
bavaria.addChild("Middle Franconia", "Ansbach");
|
|
|
|
|
bavaria.addChild("Lower Franconia", "Würzburg");
|
|
|
|
|
|
|
|
|
|
TreeRow &hessia = europe[0].addChild("Hessia", "Wiesbaden");
|
|
|
|
|
hessia.addChild("Upper Hesse", "Giessen");
|
|
|
|
|
hessia.addChild("Lower Hesse", "Darmstadt");
|
|
|
|
|
hessia.addChild("North Hesse", "Kassel");
|
|
|
|
|
|
|
|
|
|
europe[1].addChild("Île-de-France", "Paris");
|
|
|
|
|
europe[1].addChild("Provence-Alpes-Côte d'Azur", "Marseille");
|
|
|
|
|
europe[1].addChild("Auvergne-Rhône-Alpes", "Lyon");
|
|
|
|
|
europe[1].addChild("Nouvelle-Aquitaine", "Bordeaux");
|
|
|
|
|
|
|
|
|
|
europe[2].addChild("Vienna", "Vienna");
|
|
|
|
|
europe[2].addChild("Lower Austria", "St. Pölten");
|
|
|
|
|
europe[2].addChild("Upper Austria", "Linz");
|
|
|
|
|
europe[2].addChild("Styria", "Graz");
|
|
|
|
|
europe[2].addChild("Carinthia", "Klagenfurt");
|
|
|
|
|
europe[2].addChild("Salzburg", "Salzburg");
|
|
|
|
|
europe[2].addChild("Tyrol", "Innsbruck");
|
|
|
|
|
europe[2].addChild("Vorarlberg", "Bregenz");
|
|
|
|
|
europe[2].addChild("Burgenland", "Eisenstadt");
|
|
|
|
|
|
|
|
|
|
return new QRangeModel(std::ref(europe));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
struct QMetaMethodEnumerator
|
|
|
|
|
{
|
|
|
|
|
struct iterator
|
|
|
|
|
{
|
|
|
|
|
using difference_type = int;
|
|
|
|
|
using size_type = int;
|
|
|
|
|
using pointer = void;
|
|
|
|
|
using iterator_category = std::forward_iterator_tag;
|
|
|
|
|
using value_type = std::tuple<int, QByteArray, QMetaMethod>;
|
|
|
|
|
using reference = value_type;
|
|
|
|
|
using const_reference = const value_type;
|
|
|
|
|
|
|
|
|
|
friend constexpr iterator &operator++(iterator &that)
|
|
|
|
|
{ ++that.m_index; return that; }
|
|
|
|
|
friend constexpr iterator operator++(iterator &that, int)
|
|
|
|
|
{ auto copy = that; ++that.m_index; return copy; }
|
|
|
|
|
friend constexpr iterator &operator+=(iterator &that, int n)
|
|
|
|
|
{ that.m_index += n; return that; }
|
|
|
|
|
|
|
|
|
|
friend constexpr bool comparesEqual(const iterator &lhs, const iterator &rhs) noexcept
|
|
|
|
|
{
|
|
|
|
|
return lhs.m_index == rhs.m_index && lhs.m_metaobject == rhs.m_metaobject;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
friend constexpr Qt::strong_ordering compareThreeWay(const iterator &lhs,
|
|
|
|
|
const iterator &rhs) noexcept
|
|
|
|
|
{
|
|
|
|
|
return Qt::compareThreeWay(lhs.m_index, rhs.m_index);
|
|
|
|
|
}
|
|
|
|
|
Q_DECLARE_STRONGLY_ORDERED(iterator)
|
|
|
|
|
|
|
|
|
|
const_reference operator*() const
|
|
|
|
|
{ return {m_index,
|
|
|
|
|
m_metaobject->method(m_index).name().slice(4),
|
|
|
|
|
m_metaobject->method(m_index)}; }
|
|
|
|
|
|
|
|
|
|
const QMetaObject *m_metaobject = nullptr;
|
|
|
|
|
int m_index = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static_assert(std::input_iterator<iterator>);
|
|
|
|
|
|
|
|
|
|
using size_type = iterator::size_type;
|
|
|
|
|
using value_type = iterator::value_type;
|
|
|
|
|
using const_iterator = iterator;
|
|
|
|
|
|
|
|
|
|
const_iterator begin() const { return iterator{&m_metaobject, m_metaobject.methodOffset()}; }
|
|
|
|
|
const_iterator end() const { return iterator{&m_metaobject, size()}; }
|
|
|
|
|
size_type size() const { return m_metaobject.methodCount() - m_metaobject.methodOffset(); }
|
|
|
|
|
|
|
|
|
|
explicit QMetaMethodEnumerator(const QMetaObject &mo) noexcept
|
|
|
|
|
: m_metaobject(mo)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
template <typename Class>
|
|
|
|
|
static QMetaMethodEnumerator fromType() noexcept
|
|
|
|
|
{
|
|
|
|
|
return QMetaMethodEnumerator{Class::staticMetaObject};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
const QMetaObject &m_metaobject;
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-11 16:14:26 +00:00
|
|
|
|
class MainWindow : public QMainWindow
|
2025-08-08 07:05:55 +00:00
|
|
|
|
{
|
2025-08-11 16:14:26 +00:00
|
|
|
|
Q_OBJECT
|
|
|
|
|
public:
|
|
|
|
|
MainWindow(QWidget *parent = nullptr)
|
|
|
|
|
: QMainWindow(parent), model(nullptr)
|
|
|
|
|
{
|
|
|
|
|
QSplitter *splitter = new QSplitter;
|
|
|
|
|
|
|
|
|
|
treeview = new QTreeView;
|
|
|
|
|
treeview->setUniformRowHeights(true);
|
|
|
|
|
splitter->addWidget(treeview);
|
2025-08-08 07:05:55 +00:00
|
|
|
|
|
|
|
|
|
#ifdef QUICK_UI
|
2025-08-11 16:14:26 +00:00
|
|
|
|
quickWidget = new QQuickWidget;
|
|
|
|
|
quickWidget->loadFromModule("Main", "Main");
|
|
|
|
|
splitter->addWidget(quickWidget);
|
2025-08-08 07:05:55 +00:00
|
|
|
|
#endif
|
|
|
|
|
|
2025-08-11 16:14:26 +00:00
|
|
|
|
setCentralWidget(splitter);
|
|
|
|
|
|
|
|
|
|
QComboBox *modelPicker = new QComboBox;
|
|
|
|
|
connect(modelPicker, &QComboBox::currentIndexChanged, this, &MainWindow::modelChanged);
|
|
|
|
|
// this will implicitly run modelChanged()
|
|
|
|
|
modelPicker->setModel(new QRangeModel(QMetaMethodEnumerator::fromType<ModelFactory>(),
|
|
|
|
|
modelPicker));
|
|
|
|
|
modelPicker->setModelColumn(1);
|
|
|
|
|
|
|
|
|
|
QToolBar *toolBar = addToolBar(tr("Model Operations"));
|
|
|
|
|
toolBar->addWidget(modelPicker);
|
|
|
|
|
|
|
|
|
|
QAction *addAction = toolBar->addAction(tr("Add"), this, &MainWindow::onAdd);
|
|
|
|
|
addAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ListAdd));
|
|
|
|
|
QAction *removeAction = toolBar->addAction(tr("Remove"), this, &MainWindow::onRemove);
|
|
|
|
|
removeAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ListRemove));
|
|
|
|
|
QAction *upAction = toolBar->addAction(tr("Move up"), this, &MainWindow::onUp);
|
|
|
|
|
upAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::GoUp));
|
|
|
|
|
QAction *downAction = toolBar->addAction(tr("Move down"), this, &MainWindow::onDown);
|
|
|
|
|
downAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::GoDown));
|
|
|
|
|
QAction *indentAction = toolBar->addAction(tr("Move in"), this, &MainWindow::onIn);
|
|
|
|
|
indentAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::FormatIndentMore));
|
|
|
|
|
QAction *dedentAction = toolBar->addAction(tr("Move out"), this, &MainWindow::onOut);
|
|
|
|
|
dedentAction->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::FormatIndentLess));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void modelChanged(int index)
|
|
|
|
|
{
|
|
|
|
|
QAbstractItemModel *oldModel = model;
|
2025-08-08 07:05:55 +00:00
|
|
|
|
|
|
|
|
|
const QMetaObject &mo = ModelFactory::staticMetaObject;
|
|
|
|
|
const QMetaMethod method = mo.method(index + mo.methodOffset());
|
2025-08-11 16:14:26 +00:00
|
|
|
|
if (method.invoke(&factory, qReturnArg(model))) {
|
|
|
|
|
treeview->setModel(model);
|
2025-08-08 07:05:55 +00:00
|
|
|
|
#ifdef QUICK_UI
|
2025-08-11 16:14:26 +00:00
|
|
|
|
if (!quickWidget->rootObject())
|
|
|
|
|
statusBar()->showMessage(tr("Failed to load QML"));
|
|
|
|
|
else
|
|
|
|
|
quickWidget->rootObject()->setProperty("model", QVariant::fromValue(model));
|
2025-08-08 07:05:55 +00:00
|
|
|
|
#endif
|
|
|
|
|
}
|
2025-08-11 16:14:26 +00:00
|
|
|
|
|
|
|
|
|
delete oldModel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onAdd()
|
|
|
|
|
{
|
|
|
|
|
const auto current = treeview->currentIndex();
|
|
|
|
|
showMessage(tr("Inserting after '%1'").arg(current.data().toString()));
|
2025-08-08 07:05:55 +00:00
|
|
|
|
if (!model->insertRows(current.row() + 1, 1, current.parent())) {
|
2025-08-11 16:14:26 +00:00
|
|
|
|
showMessage(tr("Insertion failed"));
|
2025-08-08 07:05:55 +00:00
|
|
|
|
} else {
|
|
|
|
|
const auto newIndex = model->index(current.row() + 1, 0, current.parent());
|
|
|
|
|
static int counter = 0;
|
|
|
|
|
model->setData(newIndex, u"New Value %1"_s.arg(++counter));
|
|
|
|
|
}
|
2025-08-11 16:14:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onRemove()
|
|
|
|
|
{
|
|
|
|
|
const auto current = treeview->currentIndex();
|
|
|
|
|
showMessage(tr("Removing '%1'").arg(current.data().toString()));
|
2025-08-08 07:05:55 +00:00
|
|
|
|
if (!model->removeRows(current.row(), 1, model->parent(current)))
|
2025-08-11 16:14:26 +00:00
|
|
|
|
showMessage(tr("Removal failed"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onUp()
|
|
|
|
|
{
|
|
|
|
|
const auto current = treeview->currentIndex();
|
|
|
|
|
showMessage(tr("Moving '%1' up").arg(current.data().toString()));
|
2025-08-08 07:05:55 +00:00
|
|
|
|
const auto currentParent = current.parent();
|
|
|
|
|
if (!model->moveRows(currentParent, current.row(), 1, currentParent, current.row() - 1))
|
2025-08-11 16:14:26 +00:00
|
|
|
|
showMessage(tr("Failed to move up"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onDown()
|
|
|
|
|
{
|
|
|
|
|
const auto current = treeview->currentIndex();
|
|
|
|
|
showMessage(tr("Moving '%1' down").arg(current.data().toString()));
|
2025-08-08 07:05:55 +00:00
|
|
|
|
const auto currentParent = current.parent();
|
|
|
|
|
if (!model->moveRows(currentParent, current.row(), 1, currentParent, current.row() + 2))
|
2025-08-11 16:14:26 +00:00
|
|
|
|
showMessage(tr("Failed to move down"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onIn()
|
|
|
|
|
{
|
|
|
|
|
const auto current = treeview->currentIndex();
|
|
|
|
|
showMessage(tr("Moving '%1' in").arg(current.data().toString()));
|
2025-08-08 07:05:55 +00:00
|
|
|
|
const auto currentParent = current.parent();
|
|
|
|
|
const auto newParent = current.sibling(current.row() - 1, 0);
|
|
|
|
|
// move the selected row under it's top-most sibling
|
|
|
|
|
|
|
|
|
|
if (!model->moveRows(currentParent, current.row(), 1, newParent, model->rowCount(newParent)))
|
2025-08-11 16:14:26 +00:00
|
|
|
|
showMessage(tr("Indentation failed"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onOut()
|
|
|
|
|
{
|
|
|
|
|
const auto current = treeview->currentIndex();
|
|
|
|
|
showMessage(tr("Moving '%1' out").arg(current.data().toString()));
|
2025-08-08 07:05:55 +00:00
|
|
|
|
const auto currentParent = current.parent();
|
|
|
|
|
const auto grandParent = currentParent.parent();
|
|
|
|
|
// move the selected row under it's grandparent
|
|
|
|
|
if (!model->moveRows(currentParent, current.row(), 1, grandParent, currentParent.row()))
|
2025-08-11 16:14:26 +00:00
|
|
|
|
showMessage(tr("Dedentation failed"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void showMessage(const QString &message, int timeout = 2000)
|
|
|
|
|
{
|
|
|
|
|
statusBar()->showMessage(message, timeout);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ModelFactory factory;
|
|
|
|
|
QRangeModel *model;
|
|
|
|
|
QTreeView *treeview;
|
2025-08-22 12:17:31 +00:00
|
|
|
|
#ifdef QUICK_UI
|
2025-08-11 16:14:26 +00:00
|
|
|
|
QQuickWidget *quickWidget;
|
2025-08-22 12:17:31 +00:00
|
|
|
|
#endif
|
2025-08-11 16:14:26 +00:00
|
|
|
|
};
|
2025-08-08 07:05:55 +00:00
|
|
|
|
|
2025-08-11 16:14:26 +00:00
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
QApplication app(argc, argv);
|
2025-08-08 07:05:55 +00:00
|
|
|
|
|
2025-08-11 16:14:26 +00:00
|
|
|
|
MainWindow mainWindow;
|
|
|
|
|
mainWindow.show();
|
2025-08-08 07:05:55 +00:00
|
|
|
|
|
2025-08-11 16:14:26 +00:00
|
|
|
|
return app.exec();
|
2025-08-08 07:05:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#include "main.moc"
|