Files
qt-creator/tests/manual/layoutbuilder/v2/lb.h

512 lines
12 KiB
C
Raw Normal View History

LayoutBuilder: Complete experimental implementation This adds support for inheritance to the existing experimental implementation in tests/manual/layoutbuilder/experimental and gets rid of the tight coupling and qobject_casts in the setter implementations. Plan is to use this (minus "bindings" via *::Id / id()) for utils/layoutbuilder.{h,cpp} later. The "binding" support via id() is still experimental, and in its current version not really useful. A possible idea would be to re-use the Tasking::Storage idea, but it's not quite clear how to expose that "long distance" (i.e. across multiple, unrelated top-level builders). However, this is not used in in current uses of the "old" layoutbuilder, so this is not blocking anything. Some notes: The *Interface hierarchy is not strictly needed, it could directly act on things in the QObject hierarchy but would then need #includes of all "buildable" classes, which can be avoided in the current implementation. Besides, the indirection allows us to tweak and/or add functionailty to the Qt classes in the indirecting code, that does not necessarily have to match 1:1 to the underlyings Qt classes. The std::function based callbacks are quite fat and not functionally needed and could be dropped by "inlining" the relevant bits from typical std::function implementations. However, these invariably seem to end up calling functions through pointers to (ABI-compatible, but) different types, which is for /user/ code formally undefined behavior according to C++11 §5.2.10/6. To avoid a discussion whether doing the same ourselves is tolerable or not, this uses std::function and pays the price of the overhead. Change-Id: I6d40c1bd48cf065fcf211eaff8d9a2298bca20eb Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
2024-05-03 15:04:54 +02:00
// Copyright (C) 2023 André Pönitz
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#pragma once
#include <QList>
#include <QString>
#include <array>
#include <cstring>
#include <initializer_list>
#if defined(UTILS_LIBRARY)
# define QTCREATOR_UTILS_EXPORT Q_DECL_EXPORT
#elif defined(UTILS_STATIC_LIBRARY)
# define QTCREATOR_UTILS_EXPORT
#else
# define QTCREATOR_UTILS_EXPORT Q_DECL_IMPORT
#endif
QT_BEGIN_NAMESPACE
class QBoxLayout;
class QFormLayout;
class QGridLayout;
class QGroupBox;
class QLabel;
class QLayout;
class QMargins;
class QObject;
class QPushButton;
class QSpinBox;
class QSplitter;
class QStackedWidget;
class QTabWidget;
class QTextEdit;
class QToolBar;
class QWidget;
QT_END_NAMESPACE
namespace Layouting {
struct LayoutItem;
// struct NestId {};
struct NestId {};
template <typename T1, typename T2>
struct IdAndArg
{
IdAndArg(const T1 &id, const T2 &arg) : id(id), arg(arg) {}
T1 id;
T2 arg;
};
// The main dispatchers
void doit(auto x, auto id, auto a);
// BuilderItem
template <typename X, typename XInterface>
struct BuilderItem : XInterface
{
public:
struct I
{
// Nested child object
template <typename Inner>
I(const Inner &p)
{
apply = [p](XInterface *x) { doit(x, NestId{}, p); };
}
// Property setter
template <typename Id, typename Arg1>
I(const IdAndArg<Id, Arg1> &p)
{
apply = [p](XInterface *x) { doit(x, p.id, p.arg); };
}
std::function<void(XInterface *)> apply;
};
void create()
{
XInterface::ptr = new XInterface::Implementation;
}
void adopt(XInterface::Implementation *ptr)
{
XInterface::ptr = ptr;
}
void apply(const I &init)
{
init.apply(this);
}
using Id = typename XInterface::Implementation *;
};
//////////////////////////////////////////////
struct LayoutInterface;
struct WidgetInterface;
struct QTCREATOR_UTILS_EXPORT LayoutItem
{
LayoutItem();
LayoutItem(QLayout *l) : layout(l), empty(!l) {}
LayoutItem(QWidget *w) : widget(w), empty(!w) {}
LayoutItem(const QString &t) : text(t) {}
LayoutItem(const LayoutInterface &inner);
LayoutItem(const WidgetInterface &inner);
~LayoutItem();
QString text;
QLayout *layout = nullptr;
QWidget *widget = nullptr;
int space = -1;
int stretch = -1;
int spanCols = 1;
int spanRows = 1;
bool empty = false;
bool break_ = false;
};
using LayoutItems = QList<LayoutItem>;
// We need two classes for each user visible Builder type.
// The actual Builder classes derive from BuilderItem with two parameters,
// one is the Builder class itself for CRTP and a one "Interface" type.
// The "Interface" types act (individually, and as a hierarchy) as interface
// members of the real QObject/QWidget/... hierarchy things. This wrapper is not
// strictly needed, the Q* hierarchy could be used directly, at the
// price of #include'ing the definitions of each participating class,
// which does not scale well.
//
// Basic
//
struct QTCREATOR_UTILS_EXPORT ThingInterface
{
template <typename T>
T *access_() const { return static_cast<T *>(ptr); }
void *ptr; // The product.
};
struct QTCREATOR_UTILS_EXPORT ObjectInterface : ThingInterface
{
using Implementation = QObject;
};
struct QTCREATOR_UTILS_EXPORT Object : BuilderItem<Object, ObjectInterface>
{
Object(std::initializer_list<I> ps);
};
//
// Layouts
//
struct QTCREATOR_UTILS_EXPORT LayoutInterface : ObjectInterface
{
using Implementation = QLayout;
void span(int cols, int rows);
void noMargin();
void normalMargin();
void customMargin(const QMargins &margin);
void addItem(const LayoutItem &item);
void flush();
void fieldGrowthPolicy(int policy);
QFormLayout *asForm();
QGridLayout *asGrid();
QBoxLayout *asBox();
std::vector<LayoutItem> pendingItems;
// Grid-only
int currentGridColumn = 0;
int currentGridRow = 0;
Qt::Alignment align = {};
};
struct QTCREATOR_UTILS_EXPORT Layout : BuilderItem<Layout, LayoutInterface>
{
};
struct QTCREATOR_UTILS_EXPORT Column : BuilderItem<Column, LayoutInterface>
{
Column(std::initializer_list<I> ps);
};
struct QTCREATOR_UTILS_EXPORT Row : BuilderItem<Row, LayoutInterface>
{
Row(std::initializer_list<I> ps);
};
struct QTCREATOR_UTILS_EXPORT Form : BuilderItem<Form, LayoutInterface>
{
Form(std::initializer_list<I> ps);
};
struct QTCREATOR_UTILS_EXPORT Grid : BuilderItem<Grid, LayoutInterface>
{
Grid(std::initializer_list<I> ps);
};
struct QTCREATOR_UTILS_EXPORT Flow : BuilderItem<Flow, LayoutInterface>
{
Flow(std::initializer_list<I> ps);
};
struct QTCREATOR_UTILS_EXPORT Stretch : LayoutItem
{
explicit Stretch(int stretch) { this->stretch = stretch; }
};
struct QTCREATOR_UTILS_EXPORT Space : LayoutItem
{
explicit Space(int space) { this->space = space; }
};
struct QTCREATOR_UTILS_EXPORT Span : LayoutItem
{
Span(int n, const LayoutItem &item);
};
//
// Widgets
//
struct QTCREATOR_UTILS_EXPORT WidgetInterface : ObjectInterface
{
using Implementation = QWidget;
QWidget *emerge();
void show();
void resize(int, int);
void setLayout(const LayoutInterface &layout);
void setWindowTitle(const QString &);
void setToolTip(const QString &);
void noMargin();
void normalMargin();
void customMargin(const QMargins &margin);
};
struct QTCREATOR_UTILS_EXPORT Widget : BuilderItem<Widget, WidgetInterface>
{
Widget(std::initializer_list<I> ps);
};
// Label
struct QTCREATOR_UTILS_EXPORT LabelInterface : WidgetInterface
{
using Implementation = QLabel;
void setText(const QString &);
};
struct QTCREATOR_UTILS_EXPORT Label : BuilderItem<Label, LabelInterface>
{
Label(std::initializer_list<I> ps);
Label(const QString &text);
};
// Group
struct QTCREATOR_UTILS_EXPORT GroupInterface : WidgetInterface
{
using Implementation = QGroupBox;
void setTitle(const QString &);
};
struct QTCREATOR_UTILS_EXPORT Group : BuilderItem<Group, GroupInterface>
{
Group(std::initializer_list<I> ps);
};
// SpinBox
struct QTCREATOR_UTILS_EXPORT SpinBoxInterface : WidgetInterface
{
using Implementation = QSpinBox;
void setValue(int);
void onTextChanged(const std::function<void(QString)> &);
};
struct QTCREATOR_UTILS_EXPORT SpinBox : BuilderItem<SpinBox, SpinBoxInterface>
{
SpinBox(std::initializer_list<I> ps);
};
// PushButton
struct QTCREATOR_UTILS_EXPORT PushButtonInterface : WidgetInterface
{
using Implementation = QPushButton;
void setText(const QString &);
void onClicked(const std::function<void()> &);
};
struct QTCREATOR_UTILS_EXPORT PushButton : BuilderItem<PushButton, PushButtonInterface>
{
PushButton(std::initializer_list<I> ps);
};
// TextEdit
struct QTCREATOR_UTILS_EXPORT TextEditInterface : WidgetInterface
{
using Implementation = QTextEdit;
void setText(const QString &);
};
struct QTCREATOR_UTILS_EXPORT TextEdit : BuilderItem<TextEdit, TextEditInterface>
{
TextEdit(std::initializer_list<I> ps);
};
// Splitter
struct QTCREATOR_UTILS_EXPORT SplitterInterface : WidgetInterface
{
using Implementation = QSplitter;
};
struct QTCREATOR_UTILS_EXPORT Splitter : BuilderItem<Splitter, SplitterInterface>
{
Splitter(std::initializer_list<I> items);
};
// Stack
struct QTCREATOR_UTILS_EXPORT StackInterface : WidgetInterface
{
};
struct QTCREATOR_UTILS_EXPORT Stack : BuilderItem<Stack, StackInterface>
{
Stack() : Stack({}) {}
Stack(std::initializer_list<I> items);
};
// TabWidget
struct QTCREATOR_UTILS_EXPORT TabInterface : WidgetInterface
{
};
struct QTCREATOR_UTILS_EXPORT TabWidgetInterface : WidgetInterface
{
using Implementation = QTabWidget;
};
struct QTCREATOR_UTILS_EXPORT Tab : BuilderItem<Tab, TabInterface>
{
Tab(const QString &tabName, const LayoutItem &item);
};
struct QTCREATOR_UTILS_EXPORT TabWidget : BuilderItem<TabWidget, TabWidgetInterface>
{
TabWidget(std::initializer_list<I> items);
};
// ToolBar
struct QTCREATOR_UTILS_EXPORT ToolBarInterface : WidgetInterface
{
using Implementation = QToolBar;
};
struct QTCREATOR_UTILS_EXPORT ToolBar : BuilderItem<ToolBar, ToolBarInterface>
{
ToolBar(std::initializer_list<I> items);
};
// Special
struct QTCREATOR_UTILS_EXPORT If : LayoutItem
{
If(bool condition, const LayoutItems &item, const LayoutItems &other = {});
};
//
// Dispatchers
//
// We need one 'Id' (and a corresponding function wrapping arguments into a
// tuple marked by this id) per 'name' of "backend" setter member function,
// i.e. one 'text' is sufficient for QLabel::setText, QLineEdit::setText.
// The name of the Id does not have to match the backend names as it
// is mapped per-backend-type in the respective setter implementation
// but we assume that it generally makes sense to stay close to the
// wrapped API name-wise.
// These are free functions overloaded on the type of builder object
// and setter id. The function implementations are independent, but
// the base expectation is that they will forwards to the backend
// type's setter.
// Special dispatchers :w
struct BindToId {};
template <typename T>
auto bindTo(T **x)
{
// FIXME: Evil hack to shut up clang-tidy which does not see that the returned tuple will
// result in an assignment to *x and complains about every use of the bound value later.
// main.cpp:129:5: Called C++ object pointer is null [clang-analyzer-core.CallAndMessage]
// 1: Calling 'bindTo<QWidget>' in /data/dev/creator/tests/manual/layoutbuilder/v2/main.cpp:73
// 2: Null pointer value stored to 'w' in /data/dev/creator/tests/manual/layoutbuilder/v2/lb.h:518
// 3: Returning from 'bindTo<QWidget>' in /data/dev/creator/tests/manual/layoutbuilder/v2/main.cpp:73
// 4: Called C++ object pointer is null in /data/dev/creator/tests/manual/layoutbuilder/v2/main.cpp:129
*x = reinterpret_cast<T*>(1);
return IdAndArg{BindToId{}, x};
}
template <typename Interface>
void doit(Interface *x, BindToId, auto p)
{
*p = static_cast<Interface::Implementation *>(x->ptr);
}
struct IdId {};
auto id(auto x) { return IdAndArg{IdId{}, x}; }
template <typename Interface>
void doit(Interface *x, IdId, auto p)
{
*p = static_cast<Interface::Implementation *>(x->ptr);
}
// Setter dispatchers
struct SizeId {};
auto size(auto w, auto h) { return IdAndArg{SizeId{}, std::pair{w, h}}; }
void doit(auto x, SizeId, auto p) { x->resize(p.first, p.second); }
struct TextId {};
auto text(auto x) { return IdAndArg{TextId{}, x}; }
void doit(auto x, TextId, auto t) { x->setText(t); }
struct TitleId {};
auto title(auto x) { return IdAndArg{TitleId{}, x}; }
void doit(auto x, TitleId, auto t) { x->setTitle(t); }
struct ToolTipId {};
auto toolTip(auto x) { return IdAndArg{ToolTipId{}, x}; }
void doit(auto x, ToolTipId, auto t) { x->setToolTip(t); }
struct WindowTitleId {};
auto windowTitle(auto x) { return IdAndArg{WindowTitleId{}, x}; }
void doit(auto x, WindowTitleId, auto t) { x->setWindowTitle(t); }
struct OnTextChangedId {};
auto onTextChanged(auto x) { return IdAndArg{OnTextChangedId{}, x}; }
void doit(auto x, OnTextChangedId, auto func) { x->onTextChanged(func); }
struct OnClickedId {};
auto onClicked(auto x) { return IdAndArg{OnClickedId{}, x}; }
void doit(auto x, OnClickedId, auto func) { x->onClicked(func); }
// Nesting dispatchers
QTCREATOR_UTILS_EXPORT void addNestedItem(WidgetInterface *widget, const LayoutInterface &layout);
QTCREATOR_UTILS_EXPORT void addNestedItem(LayoutInterface *layout, const LayoutItem &inner);
QTCREATOR_UTILS_EXPORT void addNestedItem(LayoutInterface *layout, const LayoutInterface &inner);
QTCREATOR_UTILS_EXPORT void addNestedItem(LayoutInterface *layout, const WidgetInterface &inner);
QTCREATOR_UTILS_EXPORT void addNestedItem(LayoutInterface *layout, const QString &inner);
QTCREATOR_UTILS_EXPORT void addNestedItem(LayoutInterface *layout, const std::function<LayoutItem()> &inner);
// ... can be added to anywhere later to support "user types"
void doit(auto outer, NestId, auto inner)
{
addNestedItem(outer, inner);
}
// Special layout items
QTCREATOR_UTILS_EXPORT LayoutItem br();
QTCREATOR_UTILS_EXPORT LayoutItem empty();
QTCREATOR_UTILS_EXPORT LayoutItem hr();
QTCREATOR_UTILS_EXPORT LayoutItem withFormAlignment();
QTCREATOR_UTILS_EXPORT LayoutItem st();
// Convenience
QTCREATOR_UTILS_EXPORT QWidget *createHr(QWidget *parent = nullptr);
} // Layouting