qtbase/tests/manual/corelib/itemmodels/qrangemodel/main.cpp

615 lines
19 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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>
#include <QQuickWidget>
#include <QQuickItem>
#elif defined(Q_CC_MSVC)
#pragma message "Building without Quick UI"
#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};
std::array<int, 1000000> largeArray = {};
public slots:
QRangeModel *makeNumbers()
{
return new QRangeModel(&numbers);
}
QRangeModel *makeLargeArray()
{
return new QRangeModel(&largeArray);
}
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);
}
QRangeModel *makeListOfArrays()
{
QList<std::array<double, 2000>> data = {
{0.0},
{1.1},
{2.2},
};
return new QRangeModel(std::move(data));
}
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);
}
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},
});
}
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; });
return new QRangeModel(view);
}
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;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr)
: QMainWindow(parent), model(nullptr)
{
QSplitter *splitter = new QSplitter;
treeview = new QTreeView;
treeview->setUniformRowHeights(true);
splitter->addWidget(treeview);
#ifdef QUICK_UI
quickWidget = new QQuickWidget;
quickWidget->loadFromModule("Main", "Main");
splitter->addWidget(quickWidget);
#endif
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;
const QMetaObject &mo = ModelFactory::staticMetaObject;
const QMetaMethod method = mo.method(index + mo.methodOffset());
if (method.invoke(&factory, qReturnArg(model))) {
treeview->setModel(model);
#ifdef QUICK_UI
if (!quickWidget->rootObject())
statusBar()->showMessage(tr("Failed to load QML"));
else
quickWidget->rootObject()->setProperty("model", QVariant::fromValue(model));
#endif
}
delete oldModel;
}
void onAdd()
{
const auto current = treeview->currentIndex();
showMessage(tr("Inserting after '%1'").arg(current.data().toString()));
if (!model->insertRows(current.row() + 1, 1, current.parent())) {
showMessage(tr("Insertion failed"));
} 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));
}
}
void onRemove()
{
const auto current = treeview->currentIndex();
showMessage(tr("Removing '%1'").arg(current.data().toString()));
if (!model->removeRows(current.row(), 1, model->parent(current)))
showMessage(tr("Removal failed"));
}
void onUp()
{
const auto current = treeview->currentIndex();
showMessage(tr("Moving '%1' up").arg(current.data().toString()));
const auto currentParent = current.parent();
if (!model->moveRows(currentParent, current.row(), 1, currentParent, current.row() - 1))
showMessage(tr("Failed to move up"));
}
void onDown()
{
const auto current = treeview->currentIndex();
showMessage(tr("Moving '%1' down").arg(current.data().toString()));
const auto currentParent = current.parent();
if (!model->moveRows(currentParent, current.row(), 1, currentParent, current.row() + 2))
showMessage(tr("Failed to move down"));
}
void onIn()
{
const auto current = treeview->currentIndex();
showMessage(tr("Moving '%1' in").arg(current.data().toString()));
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)))
showMessage(tr("Indentation failed"));
}
void onOut()
{
const auto current = treeview->currentIndex();
showMessage(tr("Moving '%1' out").arg(current.data().toString()));
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()))
showMessage(tr("Dedentation failed"));
}
void showMessage(const QString &message, int timeout = 2000)
{
statusBar()->showMessage(message, timeout);
}
ModelFactory factory;
QRangeModel *model;
QTreeView *treeview;
#ifdef QUICK_UI
QQuickWidget *quickWidget;
#endif
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow mainWindow;
mainWindow.show();
return app.exec();
}
#include "main.moc"