forked from qt-creator/qt-creator
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>
This commit is contained in:
17
tests/manual/layoutbuilder/v2/CMakeLists.txt
Normal file
17
tests/manual/layoutbuilder/v2/CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
|
project(lb LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
|
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
add_executable(lb lb.h lb.cpp main.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(lb PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
|
935
tests/manual/layoutbuilder/v2/lb.cpp
Normal file
935
tests/manual/layoutbuilder/v2/lb.cpp
Normal file
@@ -0,0 +1,935 @@
|
|||||||
|
// Copyright (C) 2020 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#include "lb.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QGridLayout>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSpacerItem>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QSplitter>
|
||||||
|
#include <QStackedLayout>
|
||||||
|
#include <QStackedWidget>
|
||||||
|
#include <QStyle>
|
||||||
|
#include <QTabWidget>
|
||||||
|
#include <QTextEdit>
|
||||||
|
#include <QToolBar>
|
||||||
|
|
||||||
|
namespace Layouting {
|
||||||
|
|
||||||
|
// That's cut down qtcassert.{c,h} to avoid the dependency.
|
||||||
|
#define QTC_STRINGIFY_HELPER(x) #x
|
||||||
|
#define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x)
|
||||||
|
#define QTC_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__))
|
||||||
|
#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_STRING(#cond); action; } do {} while (0)
|
||||||
|
#define QTC_CHECK(cond) if (cond) {} else { QTC_STRING(#cond); } do {} while (0)
|
||||||
|
|
||||||
|
|
||||||
|
template <typename XInterface>
|
||||||
|
XInterface::Implementation *access(const XInterface *x)
|
||||||
|
{
|
||||||
|
return static_cast<XInterface::Implementation *>(x->ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setter implementation
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
class FlowLayout : public QLayout
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1)
|
||||||
|
: QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing)
|
||||||
|
{
|
||||||
|
setContentsMargins(margin, margin, margin, margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1)
|
||||||
|
: m_hSpace(hSpacing), m_vSpace(vSpacing)
|
||||||
|
{
|
||||||
|
setContentsMargins(margin, margin, margin, margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
~FlowLayout() override
|
||||||
|
{
|
||||||
|
QLayoutItem *item;
|
||||||
|
while ((item = takeAt(0)))
|
||||||
|
delete item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addItem(QLayoutItem *item) override { itemList.append(item); }
|
||||||
|
|
||||||
|
int horizontalSpacing() const
|
||||||
|
{
|
||||||
|
if (m_hSpace >= 0)
|
||||||
|
return m_hSpace;
|
||||||
|
else
|
||||||
|
return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
int verticalSpacing() const
|
||||||
|
{
|
||||||
|
if (m_vSpace >= 0)
|
||||||
|
return m_vSpace;
|
||||||
|
else
|
||||||
|
return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::Orientations expandingDirections() const override
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasHeightForWidth() const override { return true; }
|
||||||
|
|
||||||
|
int heightForWidth(int width) const override
|
||||||
|
{
|
||||||
|
int height = doLayout(QRect(0, 0, width, 0), true);
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count() const override { return itemList.size(); }
|
||||||
|
|
||||||
|
QLayoutItem *itemAt(int index) const override
|
||||||
|
{
|
||||||
|
return itemList.value(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize minimumSize() const override
|
||||||
|
{
|
||||||
|
QSize size;
|
||||||
|
for (QLayoutItem *item : itemList)
|
||||||
|
size = size.expandedTo(item->minimumSize());
|
||||||
|
|
||||||
|
int left, top, right, bottom;
|
||||||
|
getContentsMargins(&left, &top, &right, &bottom);
|
||||||
|
size += QSize(left + right, top + bottom);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGeometry(const QRect &rect) override
|
||||||
|
{
|
||||||
|
QLayout::setGeometry(rect);
|
||||||
|
doLayout(rect, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize sizeHint() const override
|
||||||
|
{
|
||||||
|
return minimumSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
QLayoutItem *takeAt(int index) override
|
||||||
|
{
|
||||||
|
if (index >= 0 && index < itemList.size())
|
||||||
|
return itemList.takeAt(index);
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int doLayout(const QRect &rect, bool testOnly) const
|
||||||
|
{
|
||||||
|
int left, top, right, bottom;
|
||||||
|
getContentsMargins(&left, &top, &right, &bottom);
|
||||||
|
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
|
||||||
|
int x = effectiveRect.x();
|
||||||
|
int y = effectiveRect.y();
|
||||||
|
int lineHeight = 0;
|
||||||
|
|
||||||
|
for (QLayoutItem *item : itemList) {
|
||||||
|
QWidget *wid = item->widget();
|
||||||
|
int spaceX = horizontalSpacing();
|
||||||
|
if (spaceX == -1)
|
||||||
|
spaceX = wid->style()->layoutSpacing(
|
||||||
|
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
|
||||||
|
int spaceY = verticalSpacing();
|
||||||
|
if (spaceY == -1)
|
||||||
|
spaceY = wid->style()->layoutSpacing(
|
||||||
|
QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
|
||||||
|
int nextX = x + item->sizeHint().width() + spaceX;
|
||||||
|
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
|
||||||
|
x = effectiveRect.x();
|
||||||
|
y = y + lineHeight + spaceY;
|
||||||
|
nextX = x + item->sizeHint().width() + spaceX;
|
||||||
|
lineHeight = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!testOnly)
|
||||||
|
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
|
||||||
|
|
||||||
|
x = nextX;
|
||||||
|
lineHeight = qMax(lineHeight, item->sizeHint().height());
|
||||||
|
}
|
||||||
|
return y + lineHeight - rect.y() + bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
int smartSpacing(QStyle::PixelMetric pm) const
|
||||||
|
{
|
||||||
|
QObject *parent = this->parent();
|
||||||
|
if (!parent) {
|
||||||
|
return -1;
|
||||||
|
} else if (parent->isWidgetType()) {
|
||||||
|
auto pw = static_cast<QWidget *>(parent);
|
||||||
|
return pw->style()->pixelMetric(pm, nullptr, pw);
|
||||||
|
} else {
|
||||||
|
return static_cast<QLayout *>(parent)->spacing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QLayoutItem *> itemList;
|
||||||
|
int m_hSpace;
|
||||||
|
int m_vSpace;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\namespace Layouting
|
||||||
|
\inmodule QtCreator
|
||||||
|
|
||||||
|
\brief The Layouting namespace contains classes for use with layout builders.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\class Layouting::LayoutItem
|
||||||
|
\inmodule QtCreator
|
||||||
|
|
||||||
|
\brief The LayoutItem class represents widgets, layouts, and aggregate
|
||||||
|
items for use in conjunction with layout builders.
|
||||||
|
|
||||||
|
Layout items are typically implicitly constructed when adding items to a
|
||||||
|
\c LayoutBuilder instance using \c LayoutBuilder::addItem() or
|
||||||
|
\c LayoutBuilder::addItems() and never stored in user code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Constructs a layout item instance representing an empty cell.
|
||||||
|
*/
|
||||||
|
LayoutItem::LayoutItem() = default;
|
||||||
|
|
||||||
|
LayoutItem::~LayoutItem() = default;
|
||||||
|
|
||||||
|
LayoutItem::LayoutItem(const LayoutInterface &inner)
|
||||||
|
: LayoutItem(access(&inner))
|
||||||
|
{}
|
||||||
|
|
||||||
|
LayoutItem::LayoutItem(const WidgetInterface &inner)
|
||||||
|
: LayoutItem(access(&inner))
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn template <class T> LayoutItem(const T &t)
|
||||||
|
\internal
|
||||||
|
|
||||||
|
Constructs a layout item proxy for \a t.
|
||||||
|
|
||||||
|
T could be
|
||||||
|
\list
|
||||||
|
\li \c {QString}
|
||||||
|
\li \c {QWidget *}
|
||||||
|
\li \c {QLayout *}
|
||||||
|
\endlist
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
|
||||||
|
// Object
|
||||||
|
|
||||||
|
Object::Object(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
create();
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QWidget *widgetForItem(QLayoutItem *item)
|
||||||
|
{
|
||||||
|
if (QWidget *w = item->widget())
|
||||||
|
return w;
|
||||||
|
if (item->spacerItem())
|
||||||
|
return nullptr;
|
||||||
|
if (QLayout *l = item->layout()) {
|
||||||
|
for (int i = 0, n = l->count(); i < n; ++i) {
|
||||||
|
if (QWidget *w = widgetForItem(l->itemAt(i)))
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QLabel *createLabel(const QString &text)
|
||||||
|
{
|
||||||
|
auto label = new QLabel(text);
|
||||||
|
label->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void addItemToBoxLayout(QBoxLayout *layout, const LayoutItem &item)
|
||||||
|
{
|
||||||
|
if (QWidget *w = item.widget) {
|
||||||
|
layout->addWidget(w);
|
||||||
|
} else if (QLayout *l = item.layout) {
|
||||||
|
layout->addLayout(l);
|
||||||
|
} else if (item.stretch != -1) {
|
||||||
|
layout->addStretch(item.stretch);
|
||||||
|
} else if (item.space != -1) {
|
||||||
|
layout->addSpacing(item.space);
|
||||||
|
} else if (!item.text.isEmpty()) {
|
||||||
|
layout->addWidget(createLabel(item.text));
|
||||||
|
} else if (item.empty) {
|
||||||
|
// Nothing to do, but no reason to warn, either.
|
||||||
|
} else {
|
||||||
|
QTC_CHECK(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void addItemToFlowLayout(FlowLayout *layout, const LayoutItem &item)
|
||||||
|
{
|
||||||
|
if (QWidget *w = item.widget) {
|
||||||
|
layout->addWidget(w);
|
||||||
|
} else if (QLayout *l = item.layout) {
|
||||||
|
layout->addItem(l);
|
||||||
|
// } else if (item.stretch != -1) {
|
||||||
|
// layout->addStretch(item.stretch);
|
||||||
|
// } else if (item.space != -1) {
|
||||||
|
// layout->addSpacing(item.space);
|
||||||
|
} else if (item.empty) {
|
||||||
|
// Nothing to do, but no reason to warn, either
|
||||||
|
} else if (!item.text.isEmpty()) {
|
||||||
|
layout->addWidget(createLabel(item.text));
|
||||||
|
} else {
|
||||||
|
QTC_CHECK(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// void doAddSpace(LayoutBuilder &builder, const Space &space)
|
||||||
|
// {
|
||||||
|
// ResultItem fi;
|
||||||
|
// fi.space = space.space;
|
||||||
|
// builder.stack.last().pendingItems.append(fi);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void doAddStretch(LayoutBuilder &builder, const Stretch &stretch)
|
||||||
|
// {
|
||||||
|
// Item fi;
|
||||||
|
// fi.stretch = stretch.stretch;
|
||||||
|
// builder.stack.last().pendingItems.append(fi);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void doAddLayout(LayoutBuilder &builder, QLayout *layout)
|
||||||
|
// {
|
||||||
|
// builder.stack.last().pendingItems.append(Item(layout));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void doAddWidget(LayoutBuilder &builder, QWidget *widget)
|
||||||
|
// {
|
||||||
|
// builder.stack.last().pendingItems.append(Item(widget));
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\class Layouting::Space
|
||||||
|
\inmodule QtCreator
|
||||||
|
|
||||||
|
\brief The Space class represents some empty space in a layout.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\class Layouting::Stretch
|
||||||
|
\inmodule QtCreator
|
||||||
|
|
||||||
|
\brief The Stretch class represents some stretch in a layout.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\class Layouting::LayoutBuilder
|
||||||
|
\internal
|
||||||
|
\inmodule QtCreator
|
||||||
|
|
||||||
|
\brief The LayoutBuilder class provides a convenient way to fill \c QFormLayout
|
||||||
|
and \c QGridLayouts with contents.
|
||||||
|
|
||||||
|
Filling a layout with items happens item-by-item, row-by-row.
|
||||||
|
|
||||||
|
A LayoutBuilder instance is typically used locally within a function and never stored.
|
||||||
|
|
||||||
|
\sa addItem(), addItems()
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\internal
|
||||||
|
Destructs a layout builder.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Starts a new row containing \a items. The row can be further extended by
|
||||||
|
other items using \c addItem() or \c addItems().
|
||||||
|
|
||||||
|
\sa addItem(), addItems()
|
||||||
|
*/
|
||||||
|
// void LayoutItem::addRow(const LayoutItems &items)
|
||||||
|
// {
|
||||||
|
// addItem(br);
|
||||||
|
// addItems(items);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /*!
|
||||||
|
// Adds the layout item \a item as sub items.
|
||||||
|
// */
|
||||||
|
// void LayoutItem::addItem(const LayoutItem &item)
|
||||||
|
// {
|
||||||
|
// subItems.append(item);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /*!
|
||||||
|
// Adds the layout items \a items as sub items.
|
||||||
|
// */
|
||||||
|
// void LayoutItem::addItems(const LayoutItems &items)
|
||||||
|
// {
|
||||||
|
// subItems.append(items);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /*!
|
||||||
|
// Attaches the constructed layout to the provided QWidget \a w.
|
||||||
|
|
||||||
|
// This operation can only be performed once per LayoutBuilder instance.
|
||||||
|
// */
|
||||||
|
|
||||||
|
// void LayoutItem::attachTo(QWidget *w) const
|
||||||
|
// {
|
||||||
|
// LayoutBuilder builder;
|
||||||
|
|
||||||
|
// builder.stack.append(w);
|
||||||
|
// addItemHelper(builder, *this);
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// Layout
|
||||||
|
|
||||||
|
void LayoutInterface::span(int cols, int rows)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(!pendingItems.empty(), return);
|
||||||
|
pendingItems.back().spanCols = cols;
|
||||||
|
pendingItems.back().spanRows = rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutInterface::noMargin()
|
||||||
|
{
|
||||||
|
customMargin({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutInterface::normalMargin()
|
||||||
|
{
|
||||||
|
customMargin({9, 9, 9, 9});
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutInterface::customMargin(const QMargins &margin)
|
||||||
|
{
|
||||||
|
access(this)->setContentsMargins(margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutInterface::addItem(const LayoutItem &item)
|
||||||
|
{
|
||||||
|
if (item.break_)
|
||||||
|
flush();
|
||||||
|
else
|
||||||
|
pendingItems.push_back(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNestedItem(WidgetInterface *widget, const LayoutInterface &layout)
|
||||||
|
{
|
||||||
|
widget->setLayout(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNestedItem(LayoutInterface *layout, const WidgetInterface &inner)
|
||||||
|
{
|
||||||
|
LayoutItem item;
|
||||||
|
item.widget = access(&inner);
|
||||||
|
layout->addItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNestedItem(LayoutInterface *layout, const LayoutItem &inner)
|
||||||
|
{
|
||||||
|
layout->addItem(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNestedItem(LayoutInterface *layout, const LayoutInterface &inner)
|
||||||
|
{
|
||||||
|
LayoutItem item;
|
||||||
|
item.layout = access(&inner);
|
||||||
|
layout->addItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNestedItem(LayoutInterface *layout, const std::function<LayoutItem()> &inner)
|
||||||
|
{
|
||||||
|
LayoutItem item = inner();
|
||||||
|
layout->addItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addNestedItem(LayoutInterface *layout, const QString &inner)
|
||||||
|
{
|
||||||
|
LayoutItem item;
|
||||||
|
item.text = inner;
|
||||||
|
layout->addItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutItem empty()
|
||||||
|
{
|
||||||
|
LayoutItem item;
|
||||||
|
item.empty = true;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutItem hr()
|
||||||
|
{
|
||||||
|
LayoutItem item;
|
||||||
|
item.widget = createHr();
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutItem br()
|
||||||
|
{
|
||||||
|
LayoutItem item;
|
||||||
|
item.break_ = true;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutItem st()
|
||||||
|
{
|
||||||
|
LayoutItem item;
|
||||||
|
item.stretch = 1;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFormLayout *LayoutInterface::asForm()
|
||||||
|
{
|
||||||
|
return qobject_cast<QFormLayout *>(access(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
QGridLayout *LayoutInterface::asGrid()
|
||||||
|
{
|
||||||
|
return qobject_cast<QGridLayout *>(access(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
QBoxLayout *LayoutInterface::asBox()
|
||||||
|
{
|
||||||
|
return qobject_cast<QBoxLayout *>(access(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutInterface::flush()
|
||||||
|
{
|
||||||
|
if (QGridLayout *lt = asGrid()) {
|
||||||
|
for (const LayoutItem &item : std::as_const(pendingItems)) {
|
||||||
|
Qt::Alignment a = currentGridColumn == 0 ? align : Qt::Alignment();
|
||||||
|
if (item.widget)
|
||||||
|
lt->addWidget(item.widget, currentGridRow, currentGridColumn, item.spanRows, item.spanCols, a);
|
||||||
|
else if (item.layout)
|
||||||
|
lt->addLayout(item.layout, currentGridRow, currentGridColumn, item.spanRows, item.spanCols, a);
|
||||||
|
else if (!item.text.isEmpty())
|
||||||
|
lt->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, item.spanRows, item.spanCols, a);
|
||||||
|
currentGridColumn += item.spanCols;
|
||||||
|
// Intentionally not used, use 'br'/'empty' for vertical progress.
|
||||||
|
// currentGridRow += item.spanRows;
|
||||||
|
}
|
||||||
|
++currentGridRow;
|
||||||
|
currentGridColumn = 0;
|
||||||
|
pendingItems.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QFormLayout *fl = asForm()) {
|
||||||
|
if (pendingItems.size() > 2) {
|
||||||
|
auto hbox = new QHBoxLayout;
|
||||||
|
hbox->setContentsMargins(0, 0, 0, 0);
|
||||||
|
for (int i = 1; i < pendingItems.size(); ++i)
|
||||||
|
addItemToBoxLayout(hbox, pendingItems.at(i));
|
||||||
|
while (pendingItems.size() > 1)
|
||||||
|
pendingItems.pop_back();
|
||||||
|
pendingItems.push_back(LayoutItem(hbox));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingItems.size() == 1) { // Only one item given, so this spans both columns.
|
||||||
|
const LayoutItem &f0 = pendingItems.at(0);
|
||||||
|
if (auto layout = f0.layout)
|
||||||
|
fl->addRow(layout);
|
||||||
|
else if (auto widget = f0.widget)
|
||||||
|
fl->addRow(widget);
|
||||||
|
} else if (pendingItems.size() == 2) { // Normal case, both columns used.
|
||||||
|
LayoutItem &f1 = pendingItems[1];
|
||||||
|
const LayoutItem &f0 = pendingItems.at(0);
|
||||||
|
if (!f1.widget && !f1.layout && !f1.text.isEmpty())
|
||||||
|
f1.widget = createLabel(f1.text);
|
||||||
|
|
||||||
|
// QFormLayout accepts only widgets or text in the first column.
|
||||||
|
// FIXME: Should we be more generous?
|
||||||
|
if (f0.widget) {
|
||||||
|
if (f1.layout)
|
||||||
|
fl->addRow(f0.widget, f1.layout);
|
||||||
|
else if (f1.widget)
|
||||||
|
fl->addRow(f0.widget, f1.widget);
|
||||||
|
} else {
|
||||||
|
if (f1.layout)
|
||||||
|
fl->addRow(createLabel(f0.text), f1.layout);
|
||||||
|
else if (f1.widget)
|
||||||
|
fl->addRow(createLabel(f0.text), f1.widget);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
QTC_CHECK(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up label as buddy if possible.
|
||||||
|
const int lastRow = fl->rowCount() - 1;
|
||||||
|
QLayoutItem *l = fl->itemAt(lastRow, QFormLayout::LabelRole);
|
||||||
|
QLayoutItem *f = fl->itemAt(lastRow, QFormLayout::FieldRole);
|
||||||
|
if (l && f) {
|
||||||
|
if (QLabel *label = qobject_cast<QLabel *>(l->widget())) {
|
||||||
|
if (QWidget *widget = widgetForItem(f))
|
||||||
|
label->setBuddy(widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingItems.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LayoutItem withFormAlignment()
|
||||||
|
// {
|
||||||
|
// LayoutItem item;
|
||||||
|
// item.onAdd = [](LayoutBuilder &builder) {
|
||||||
|
// if (builder.stack.size() >= 2) {
|
||||||
|
// if (auto widget = builder.stack.at(builder.stack.size() - 2).widget) {
|
||||||
|
// const Qt::Alignment align(widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment));
|
||||||
|
// builder.stack.last().align = align;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// return item;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Flow
|
||||||
|
|
||||||
|
Flow::Flow(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
adopt(new FlowLayout);
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
// for (const LayoutItem &item : std::as_const(pendingItems))
|
||||||
|
// addItemToFlowLayout(flowLayout, item);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row & Column
|
||||||
|
|
||||||
|
Row::Row(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
adopt(new QHBoxLayout);
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
auto self = asBox();
|
||||||
|
for (const LayoutItem &item : pendingItems)
|
||||||
|
addItemToBoxLayout(self, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
Column::Column(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
adopt(new QVBoxLayout);
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
auto self = asBox();
|
||||||
|
for (const LayoutItem &item : pendingItems)
|
||||||
|
addItemToBoxLayout(self, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grid
|
||||||
|
|
||||||
|
Grid::Grid(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
adopt(new QGridLayout);
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form
|
||||||
|
|
||||||
|
Form::Form(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
adopt(new QFormLayout);
|
||||||
|
fieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutInterface::fieldGrowthPolicy(int policy)
|
||||||
|
{
|
||||||
|
if (auto lt = asForm())
|
||||||
|
lt->setFieldGrowthPolicy(QFormLayout::FieldGrowthPolicy(policy));
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Widgets"
|
||||||
|
|
||||||
|
Widget::Widget(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
create();
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetInterface::resize(int w, int h)
|
||||||
|
{
|
||||||
|
access(this)->resize(w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetInterface::setLayout(const LayoutInterface &layout)
|
||||||
|
{
|
||||||
|
access(this)->setLayout(access(&layout));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetInterface::setWindowTitle(const QString &title)
|
||||||
|
{
|
||||||
|
access(this)->setWindowTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetInterface::setToolTip(const QString &title)
|
||||||
|
{
|
||||||
|
access(this)->setToolTip(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetInterface::show()
|
||||||
|
{
|
||||||
|
access(this)->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetInterface::noMargin()
|
||||||
|
{
|
||||||
|
customMargin({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetInterface::normalMargin()
|
||||||
|
{
|
||||||
|
customMargin({9, 9, 9, 9});
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetInterface::customMargin(const QMargins &margin)
|
||||||
|
{
|
||||||
|
access(this)->setContentsMargins(margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *WidgetInterface::emerge()
|
||||||
|
{
|
||||||
|
return access(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label
|
||||||
|
|
||||||
|
Label::Label(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
create();
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
Label::Label(const QString &text)
|
||||||
|
{
|
||||||
|
create();
|
||||||
|
setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LabelInterface::setText(const QString &text)
|
||||||
|
{
|
||||||
|
access(this)->setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group
|
||||||
|
|
||||||
|
Group::Group(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
create();
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupInterface::setTitle(const QString &title)
|
||||||
|
{
|
||||||
|
access(this)->setTitle(title);
|
||||||
|
access(this)->setObjectName(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpinBox
|
||||||
|
|
||||||
|
SpinBox::SpinBox(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
create();
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpinBoxInterface::setValue(int val)
|
||||||
|
{
|
||||||
|
access(this)->setValue(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpinBoxInterface::onTextChanged(const std::function<void (QString)> &func)
|
||||||
|
{
|
||||||
|
QObject::connect(access(this), &QSpinBox::textChanged, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextEdit
|
||||||
|
|
||||||
|
TextEdit::TextEdit(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
create();
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextEditInterface::setText(const QString &text)
|
||||||
|
{
|
||||||
|
access(this)->setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushButton
|
||||||
|
|
||||||
|
PushButton::PushButton(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
create();
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PushButtonInterface::setText(const QString &text)
|
||||||
|
{
|
||||||
|
access(this)->setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PushButtonInterface::onClicked(const std::function<void ()> &func)
|
||||||
|
{
|
||||||
|
QObject::connect(access(this), &QAbstractButton::clicked, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack
|
||||||
|
|
||||||
|
// We use a QStackedWidget instead of a QStackedLayout here because the latter will call
|
||||||
|
// "setVisible()" when a child is added, which can lead to the widget being spawned as a
|
||||||
|
// top-level widget. This can lead to the focus shifting away from the main application.
|
||||||
|
Stack::Stack(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
create();
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splitter
|
||||||
|
|
||||||
|
Splitter::Splitter(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
create();
|
||||||
|
access(this)->setOrientation(Qt::Vertical);
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToolBar
|
||||||
|
|
||||||
|
ToolBar::ToolBar(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
create();
|
||||||
|
access(this)->setOrientation(Qt::Horizontal);
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TabWidget
|
||||||
|
|
||||||
|
TabWidget::TabWidget(std::initializer_list<I> ps)
|
||||||
|
{
|
||||||
|
create();
|
||||||
|
for (auto && p : ps)
|
||||||
|
apply(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Special Tab
|
||||||
|
|
||||||
|
// Tab::Tab(const QString &tabName, const LayoutItem &item)
|
||||||
|
// {
|
||||||
|
// onAdd = [item](LayoutBuilder &builder) {
|
||||||
|
// auto tab = new QWidget;
|
||||||
|
// builder.stack.append(tab);
|
||||||
|
// item.attachTo(tab);
|
||||||
|
// };
|
||||||
|
// onExit = [tabName](LayoutBuilder &builder) {
|
||||||
|
// QWidget *inner = builder.stack.last().widget;
|
||||||
|
// builder.stack.pop_back();
|
||||||
|
// auto tabWidget = qobject_cast<QTabWidget *>(builder.stack.last().widget);
|
||||||
|
// QTC_ASSERT(tabWidget, return);
|
||||||
|
// tabWidget->addTab(inner, tabName);
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Special If
|
||||||
|
|
||||||
|
// If::If(bool condition, const LayoutItems &items, const LayoutItems &other)
|
||||||
|
// {
|
||||||
|
// subItems.append(condition ? items : other);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// "Properties"
|
||||||
|
|
||||||
|
// LayoutItem spacing(int spacing)
|
||||||
|
// {
|
||||||
|
// return [spacing](QObject *target) {
|
||||||
|
// if (auto layout = qobject_cast<QLayout *>(target)) {
|
||||||
|
// layout->setSpacing(spacing);
|
||||||
|
// } else {
|
||||||
|
// QTC_CHECK(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// LayoutItem columnStretch(int column, int stretch)
|
||||||
|
// {
|
||||||
|
// return [column, stretch](QObject *target) {
|
||||||
|
// if (auto grid = qobject_cast<QGridLayout *>(target)) {
|
||||||
|
// grid->setColumnStretch(column, stretch);
|
||||||
|
// } else {
|
||||||
|
// QTC_CHECK(false);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
QWidget *createHr(QWidget *parent)
|
||||||
|
{
|
||||||
|
auto frame = new QFrame(parent);
|
||||||
|
frame->setFrameShape(QFrame::HLine);
|
||||||
|
frame->setFrameShadow(QFrame::Sunken);
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span::Span(int n, const LayoutItem &item)
|
||||||
|
: LayoutItem(item)
|
||||||
|
{
|
||||||
|
spanCols = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// void createItem(LayoutItem *item, QWidget *t)
|
||||||
|
// {
|
||||||
|
// if (auto l = qobject_cast<QLabel *>(t))
|
||||||
|
// l->setTextInteractionFlags(l->textInteractionFlags() | Qt::TextSelectableByMouse);
|
||||||
|
|
||||||
|
// item->onAdd = [t](LayoutBuilder &builder) { doAddWidget(builder, t); };
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
} // Layouting
|
511
tests/manual/layoutbuilder/v2/lb.h
Normal file
511
tests/manual/layoutbuilder/v2/lb.h
Normal file
@@ -0,0 +1,511 @@
|
|||||||
|
// 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
|
79
tests/manual/layoutbuilder/v2/main.cpp
Normal file
79
tests/manual/layoutbuilder/v2/main.cpp
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
// Copyright (C) 2024 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#include "lb.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QTextEdit>
|
||||||
|
|
||||||
|
using namespace Layouting;
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
|
TextEdit::Id textId;
|
||||||
|
|
||||||
|
QWidget *w = nullptr;
|
||||||
|
QGroupBox *g = nullptr;
|
||||||
|
QLabel *l = nullptr;
|
||||||
|
|
||||||
|
Group {
|
||||||
|
bindTo(&w), // Works, as GroupInterface derives from WidgetInterface
|
||||||
|
// bindTo(&l), // Does (intentionally) not work, GroupInterface does not derive from LabelInterface
|
||||||
|
bindTo(&g),
|
||||||
|
size(300, 200),
|
||||||
|
title("HHHHHHH"),
|
||||||
|
Form {
|
||||||
|
"Hallo",
|
||||||
|
Group {
|
||||||
|
title("Title"),
|
||||||
|
Column {
|
||||||
|
Label {
|
||||||
|
text("World")
|
||||||
|
},
|
||||||
|
TextEdit {
|
||||||
|
id(&textId),
|
||||||
|
text("Och noe")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
br,
|
||||||
|
"Col",
|
||||||
|
Column {
|
||||||
|
Row { "1", "2", "3" },
|
||||||
|
Row { "3", "4", "6" }
|
||||||
|
},
|
||||||
|
br,
|
||||||
|
"Grid",
|
||||||
|
Grid {
|
||||||
|
Span { 2, QString("1111111") }, "3", br,
|
||||||
|
"3", "4", "6", br,
|
||||||
|
"4", empty, "6", br,
|
||||||
|
hr, "4", "6"
|
||||||
|
},
|
||||||
|
br,
|
||||||
|
Column {
|
||||||
|
Label {
|
||||||
|
text("Hi"),
|
||||||
|
size(30, 20)
|
||||||
|
},
|
||||||
|
Row {
|
||||||
|
SpinBox {
|
||||||
|
onTextChanged([&](const QString &text) { textId->setText(text); })
|
||||||
|
},
|
||||||
|
st,
|
||||||
|
PushButton {
|
||||||
|
text("Quit"),
|
||||||
|
onClicked(QApplication::quit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.show();
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
Reference in New Issue
Block a user