Files
qt-creator/tests/manual/layoutbuilder/v2/lb.cpp
hjk a0c8d8c0b4 LayoutBuilder: Update manual test to latest syntax changes
Change-Id: I9bf07e4ae896df0640a81847d33514b1f488a024
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
2024-07-17 14:43:38 +00:00

973 lines
22 KiB
C++

// 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 <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 X>
X::Implementation *access(const X *x)
{
return static_cast<X::Implementation *>(x->ptr);
}
template <typename X>
void apply(X *x, std::initializer_list<typename X::I> ps)
{
for (auto && p : ps)
p.apply(x);
}
// FlowLayout
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 and functions to conveniently
create layouts in code.
Classes in the namespace help to create create QLayout or QWidget derived class,
instances should be used locally within a function and never stored.
\sa Layouting::Widget, Layouting::Layout
*/
/*!
\class Layouting::Layout
\inmodule QtCreator
The Layout class is a base class for more specific builder
classes to create QLayout derived objects.
*/
/*!
\class Layouting::Widget
\inmodule QtCreator
The Widget class is a base class for more specific builder
classes to create QWidget derived objects.
*/
/*!
\class Layouting::LayoutItem
\inmodule QtCreator
The LayoutItem class is used for intermediate results
while creating layouts with a concept of rows and spans, such
as Form and Grid.
*/
Layout::LayoutItem::LayoutItem() = default;
Layout::LayoutItem::~LayoutItem() = default;
Layout::LayoutItem::LayoutItem(const LayoutModifier &inner)
{
ownerModifier = 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
*/
// Object
Object::Object(std::initializer_list<I> ps)
{
ptr = new Implementation;
apply(this, ps);
}
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;
}
using LayoutItem = Layout::LayoutItem;
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.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.empty) {
// Nothing to do, but no reason to warn, either
} else if (!item.text.isEmpty()) {
layout->addWidget(createLabel(item.text));
} else {
QTC_CHECK(false);
}
}
/*!
\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.
*/
// Layout
void Layout::span(int cols, int rows)
{
QTC_ASSERT(!pendingItems.empty(), return);
pendingItems.back().spanCols = cols;
pendingItems.back().spanRows = rows;
}
void Layout::noMargin()
{
customMargin({});
}
void Layout::normalMargin()
{
customMargin({9, 9, 9, 9});
}
void Layout::customMargin(const QMargins &margin)
{
access(this)->setContentsMargins(margin);
}
/*!
Attaches the constructed layout to the provided QWidget \a w.
This operation can only be performed once per LayoutBuilder instance.
*/
void Layout::attachTo(QWidget *widget)
{
flush();
widget->setLayout(access(this));
}
/*!
Adds the layout item \a item as sub items.
*/
void Layout::addItem(I item)
{
item.apply(this);
}
void Layout::addItemHelper(const LayoutItem &item)
{
if (QBoxLayout *lt = asBox())
addItemToBoxLayout(lt, item);
else if (FlowLayout *lt = asFlow())
addItemToFlowLayout(lt, item);
else
pendingItems.push_back(item);
}
/*!
Adds the layout items \a items as sub items.
*/
void Layout::addItems(std::initializer_list<I> items)
{
for (const I &item : items)
item.apply(this);
}
/*!
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 Layout::addRow(std::initializer_list<I> items)
{
for (const I &item : items)
item.apply(this);
flush();
}
void Layout::setSpacing(int spacing)
{
access(this)->setSpacing(spacing);
}
void Layout::setColumnStretch(int column, int stretch)
{
if (auto grid = qobject_cast<QGridLayout *>(access(this))) {
grid->setColumnStretch(column, stretch);
} else {
QTC_CHECK(false);
}
}
void addToWidget(Widget *widget, const Layout &layout)
{
layout.flush_();
access(widget)->setLayout(access(&layout));
}
void addToLayout(Layout *layout, const Widget &inner)
{
LayoutItem item;
item.widget = access(&inner);
layout->addItemHelper(item);
}
void addToLayout(Layout *layout, QWidget *inner)
{
LayoutItem item;
item.widget = inner;
layout->addItemHelper(item);
}
void addToLayout(Layout *layout, QLayout *inner)
{
LayoutItem item;
item.layout = inner;
layout->addItemHelper(item);
}
void addToLayout(Layout *layout, const Layout &inner)
{
inner.flush_();
LayoutItem item;
item.layout = access(&inner);
layout->addItemHelper(item);
}
void addToLayout(Layout *layout, const LayoutModifier &inner)
{
inner(layout);
}
void addToLayout(Layout *layout, const QString &inner)
{
LayoutItem item;
item.text = inner;
layout->addItemHelper(item);
}
void empty(Layout *iface)
{
LayoutItem item;
item.empty = true;
iface->addItemHelper(item);
}
void hr(Layout *layout)
{
layout->addItemHelper(createHr());
}
void br(Layout *iface)
{
iface->flush();
}
void st(Layout *iface)
{
LayoutItem item;
item.stretch = 1;
iface->addItemHelper(item);
}
void noMargin(Layout *iface)
{
iface->noMargin();
}
void normalMargin(Layout *iface)
{
iface->normalMargin();
}
QFormLayout *Layout::asForm()
{
return qobject_cast<QFormLayout *>(access(this));
}
QGridLayout *Layout::asGrid()
{
return qobject_cast<QGridLayout *>(access(this));
}
QBoxLayout *Layout::asBox()
{
return qobject_cast<QBoxLayout *>(access(this));
}
FlowLayout *Layout::asFlow()
{
return dynamic_cast<FlowLayout *>(access(this));
}
void Layout::flush()
{
if (pendingItems.empty())
return;
if (QGridLayout *lt = asGrid()) {
for (const LayoutItem &item : std::as_const(pendingItems)) {
Qt::Alignment a;
if (currentGridColumn == 0 && useFormAlignment) {
// if (auto widget = builder.stack.at(builder.stack.size() - 2).widget) {
// a = widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment);
}
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 (size_t i = 1; i < pendingItems.size(); ++i)
addItemToBoxLayout(hbox, pendingItems.at(i));
while (pendingItems.size() > 1)
pendingItems.pop_back();
pendingItems.push_back(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;
}
QTC_CHECK(false); // The other layouts shouldn't use flush()
}
void Layout::flush_() const
{
const_cast<Layout *>(this)->flush();
}
void withFormAlignment(Layout *iface)
{
iface->useFormAlignment = true;
}
// Flow
Flow::Flow(std::initializer_list<I> ps)
{
ptr = new FlowLayout;
apply(this, ps);
flush();
}
// Row & Column
Row::Row(std::initializer_list<I> ps)
{
ptr = new QHBoxLayout;
apply(this, ps);
flush();
}
Column::Column(std::initializer_list<I> ps)
{
ptr = new QVBoxLayout;
apply(this, ps);
flush();
}
// Grid
Grid::Grid()
{
ptr = new QGridLayout;
}
Grid::Grid(std::initializer_list<I> ps)
{
ptr = new QGridLayout;
apply(this, ps);
flush();
}
// Form
Form::Form()
{
ptr = new QFormLayout;
}
Form::Form(std::initializer_list<I> ps)
{
auto lt = new QFormLayout;
ptr = lt;
lt->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
apply(this, ps);
flush();
}
void Layout::fieldGrowthPolicy(int policy)
{
if (auto lt = asForm())
lt->setFieldGrowthPolicy(QFormLayout::FieldGrowthPolicy(policy));
}
QWidget *Layout::emerge() const
{
const_cast<Layout *>(this)->flush();
QWidget *widget = new QWidget;
widget->setLayout(access(this));
return widget;
}
// "Widgets"
Widget::Widget(std::initializer_list<I> ps)
{
ptr = new Implementation;
apply(this, ps);
}
void Widget::setSize(int w, int h)
{
access(this)->resize(w, h);
}
void Widget::setLayout(const Layout &layout)
{
access(this)->setLayout(access(&layout));
}
void Widget::setWindowTitle(const QString &title)
{
access(this)->setWindowTitle(title);
}
void Widget::setToolTip(const QString &title)
{
access(this)->setToolTip(title);
}
void Widget::show()
{
access(this)->show();
}
void Widget::noMargin(int)
{
customMargin({});
}
void Widget::normalMargin(int)
{
customMargin({9, 9, 9, 9});
}
void Widget::customMargin(const QMargins &margin)
{
access(this)->setContentsMargins(margin);
}
QWidget *Widget::emerge() const
{
return access(this);
}
// Label
Label::Label(std::initializer_list<I> ps)
{
ptr = new Implementation;
apply(this, ps);
}
Label::Label(const QString &text)
{
ptr = new Implementation;
setText(text);
}
void Label::setText(const QString &text)
{
access(this)->setText(text);
}
// Group
Group::Group(std::initializer_list<I> ps)
{
ptr = new Implementation;
apply(this, ps);
}
void Group::setTitle(const QString &title)
{
access(this)->setTitle(title);
access(this)->setObjectName(title);
}
void Group::setGroupChecker(const std::function<void (QObject *)> &checker)
{
checker(access(this));
}
// SpinBox
SpinBox::SpinBox(std::initializer_list<I> ps)
{
ptr = new Implementation;
apply(this, ps);
}
void SpinBox::setValue(int val)
{
access(this)->setValue(val);
}
void SpinBox::onTextChanged(const std::function<void (QString)> &func)
{
QObject::connect(access(this), &QSpinBox::textChanged, func);
}
// TextEdit
TextEdit::TextEdit(std::initializer_list<I> ps)
{
ptr = new Implementation;
apply(this, ps);
}
void TextEdit::setText(const QString &text)
{
access(this)->setText(text);
}
// PushButton
PushButton::PushButton(std::initializer_list<I> ps)
{
ptr = new Implementation;
apply(this, ps);
}
void PushButton::setText(const QString &text)
{
access(this)->setText(text);
}
void PushButton::onClicked(const std::function<void ()> &func, QObject *guard)
{
QObject::connect(access(this), &QAbstractButton::clicked, guard, 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)
{
ptr = new Implementation;
apply(this, ps);
}
void addToStack(Stack *stack, const Widget &inner)
{
access(stack)->addWidget(inner.emerge());
}
void addToStack(Stack *stack, const Layout &inner)
{
inner.flush_();
access(stack)->addWidget(inner.emerge());
}
void addToStack(Stack *stack, QWidget *inner)
{
access(stack)->addWidget(inner);
}
// Splitter
Splitter::Splitter(std::initializer_list<I> ps)
{
ptr = new Implementation;
access(this)->setOrientation(Qt::Vertical);
apply(this, ps);
}
void addToSplitter(Splitter *splitter, QWidget *inner)
{
access(splitter)->addWidget(inner);
}
void addToSplitter(Splitter *splitter, const Widget &inner)
{
access(splitter)->addWidget(inner.emerge());
}
void addToSplitter(Splitter *splitter, const Layout &inner)
{
inner.flush_();
access(splitter)->addWidget(inner.emerge());
}
// ToolBar
ToolBar::ToolBar(std::initializer_list<I> ps)
{
ptr = new Implementation;
apply(this, ps);
access(this)->setOrientation(Qt::Horizontal);
}
// TabWidget
TabWidget::TabWidget(std::initializer_list<I> ps)
{
ptr = new Implementation;
apply(this, ps);
}
Tab::Tab(const QString &tabName, const Layout &inner)
: tabName(tabName), inner(inner)
{}
void addToTabWidget(TabWidget *tabWidget, const Tab &tab)
{
access(tabWidget)->addTab(tab.inner.emerge(), tab.tabName);
}
// Special If
If::If(bool condition,
const std::initializer_list<Layout::I> ifcase,
const std::initializer_list<Layout::I> thencase)
: used(condition ? ifcase : thencase)
{}
void addToLayout(Layout *layout, const If &inner)
{
for (const Layout::I &item : inner.used)
item.apply(layout);
}
// Specials
QWidget *createHr(QWidget *parent)
{
auto frame = new QFrame(parent);
frame->setFrameShape(QFrame::HLine);
frame->setFrameShadow(QFrame::Sunken);
return frame;
}
Span::Span(int n, const Layout::I &item)
: item(item), spanCols(n)
{}
void addToLayout(Layout *layout, const Span &inner)
{
LayoutItem item;
layout->addItem(inner.item);
QTC_ASSERT(!layout->pendingItems.empty(), return);
layout->pendingItems.back().spanCols = inner.spanCols;
layout->pendingItems.back().spanRows = inner.spanRows;
}
LayoutModifier spacing(int space)
{
return [space](Layout *iface) { iface->setSpacing(space); };
}
void addToLayout(Layout *layout, const Space &inner)
{
if (auto lt = layout->asBox())
lt->addSpacing(inner.space);
}
void addToLayout(Layout *layout, const Stretch &inner)
{
if (auto lt = layout->asBox())
lt->addStretch(inner.stretch);
}
// 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