Utils: Avoid intermediate widgets when using LayoutBuilder

In most cases, the layout constructed in the builder was set
on a widget which in turn was put into a vbox in the actual
widget. This is not necessary, but needs some re-ordering.

Also make sure that using not-yet-parented widgets during
layout construction does not cause visible artifacts.

Change-Id: I75727a571da093d3131ea6fba467c2c646cdb6f1
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
hjk
2021-03-11 19:02:42 +01:00
parent 4151f091e3
commit 035a6ff031
25 changed files with 401 additions and 365 deletions

View File

@@ -195,7 +195,11 @@ void BaseAspect::setVisible(bool visible)
d->m_visible = visible;
for (QWidget *w : qAsConst(d->m_subWidgets)) {
QTC_ASSERT(w, continue);
w->setVisible(visible);
// This may happen during layout building. Explicit setting visibility here
// may create a show a toplevel widget for a moment until it is parented
// to some non-shown widget.
if (visible && w->parentWidget())
w->setVisible(visible);
}
}
@@ -1000,10 +1004,10 @@ void StringAspect::addToLayout(LayoutBuilder &builder)
builder.finishRow();
}
const auto useMacroExpander = [this, &builder](QWidget *w) {
const auto useMacroExpander = [this](QWidget *w) {
if (!d->m_expanderProvider)
return;
const auto chooser = new VariableChooser(builder.layout()->parentWidget());
const auto chooser = new VariableChooser(w);
chooser->addSupportedWidget(w);
chooser->addMacroExpanderProvider(d->m_expanderProvider);
};
@@ -1208,17 +1212,19 @@ void BoolAspect::addToLayout(LayoutBuilder &builder)
d->m_checkBox->setText(labelText());
builder.addItem(d->m_checkBox.data());
break;
case LabelPlacement::AtCheckBox:
case LabelPlacement::AtCheckBox: {
d->m_checkBox->setText(labelText());
builder.addItem(createSubWidget<QLabel>());
LayoutBuilder::LayoutType type = builder.layoutType();
if (type == LayoutBuilder::FormLayout)
builder.addItem(createSubWidget<QLabel>());
builder.addItem(d->m_checkBox.data());
break;
}
case LabelPlacement::InExtraLabel:
addLabeledItem(builder, d->m_checkBox);
break;
}
d->m_checkBox->setChecked(value());
builder.addItem(d->m_checkBox.data());
connect(d->m_checkBox.data(), &QAbstractButton::clicked, this, [this] {
setValue(d->m_checkBox->isChecked());
});
@@ -1965,9 +1971,12 @@ void TextDisplay::addToLayout(LayoutBuilder &builder)
d->m_label->setTextInteractionFlags(Qt::TextSelectableByMouse);
d->m_label->setElideMode(Qt::ElideNone);
d->m_label->setWordWrap(true);
// Do not use m_label->setVisible(isVisible()) unconditionally, it does not
// have a QWidget parent yet when used in a LayoutBuilder.
if (!isVisible())
d->m_label->setVisible(false);
}
builder.addItem(d->m_label.data());
d->m_label->setVisible(isVisible());
}
/*!

View File

@@ -48,8 +48,6 @@ namespace Utils {
\value Grid
\value HBox
\value VBox
\value HBoxWithMargins
\value VBoxWithMargins
*/
/*!
@@ -72,19 +70,17 @@ LayoutBuilder::LayoutItem::LayoutItem()
/*!
Constructs a layout item proxy for \a layout, spanning the number
of cells specified by \a span in the target layout, with alignment \a align.
Constructs a layout item proxy for \a layout.
*/
LayoutBuilder::LayoutItem::LayoutItem(QLayout *layout, int span, Alignment align)
: layout(layout), span(span), align(align)
LayoutBuilder::LayoutItem::LayoutItem(QLayout *layout)
: layout(layout)
{}
/*!
Constructs a layout item proxy for \a widget, spanning the number
of cell specified by \a span in the target layout, with alignment \a align.
Constructs a layout item proxy for \a widget.
*/
LayoutBuilder::LayoutItem::LayoutItem(QWidget *widget, int span, Alignment align)
: widget(widget), span(span), align(align)
LayoutBuilder::LayoutItem::LayoutItem(QWidget *widget)
: widget(widget)
{}
/*!
@@ -95,27 +91,165 @@ LayoutBuilder::LayoutItem::LayoutItem(QWidget *widget, int span, Alignment align
\sa BaseAspect::addToLayout()
*/
LayoutBuilder::LayoutItem::LayoutItem(BaseAspect &aspect, int span, Alignment align)
: aspect(&aspect), span(span), align(align)
LayoutBuilder::LayoutItem::LayoutItem(BaseAspect &aspect)
: aspect(&aspect)
{}
LayoutBuilder::LayoutItem::LayoutItem(BaseAspect *aspect, int span, Alignment align)
: aspect(aspect), span(span), align(align)
LayoutBuilder::LayoutItem::LayoutItem(BaseAspect *aspect)
: aspect(aspect)
{}
/*!
Constructs a layout item containing some static \a text.
*/
LayoutBuilder::LayoutItem::LayoutItem(const QString &text, int span, Alignment align)
: text(text), span(span), align(align)
LayoutBuilder::LayoutItem::LayoutItem(const QString &text)
: text(text)
{}
static QLayout *createLayoutFromType(LayoutBuilder::LayoutType layoutType)
{
switch (layoutType) {
case LayoutBuilder::FormLayout: {
auto formLayout = new QFormLayout;
formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
return formLayout;
}
case LayoutBuilder::GridLayout: {
auto gridLayout = new QGridLayout;
return gridLayout;
}
case LayoutBuilder::HBoxLayout: {
auto hboxLayout = new QHBoxLayout;
return hboxLayout;
}
case LayoutBuilder::VBoxLayout: {
auto vboxLayout = new QVBoxLayout;
return vboxLayout;
}
}
QTC_CHECK(false);
return nullptr;
}
static void setMargins(bool on, QLayout *layout)
{
const int d = on ? 9 : 0;
layout->setContentsMargins(d, d, d, d);
}
static void flushPendingFormItems(QFormLayout *formLayout,
LayoutBuilder::LayoutItems &pendingFormItems)
{
QTC_ASSERT(formLayout, return);
if (pendingFormItems.empty())
return;
// If there are more than two items, we cram the last ones in one hbox.
if (pendingFormItems.size() > 2) {
auto hbox = new QHBoxLayout;
setMargins(false, hbox);
for (int i = 1; i < pendingFormItems.size(); ++i) {
if (QWidget *w = pendingFormItems.at(i).widget)
hbox->addWidget(w);
else if (QLayout *l = pendingFormItems.at(i).layout)
hbox->addLayout(l);
else
QTC_CHECK(false);
}
while (pendingFormItems.size() >= 2)
pendingFormItems.pop_back();
pendingFormItems.append(LayoutBuilder::LayoutItem(hbox));
}
if (pendingFormItems.size() == 1) { // One one item given, so this spans both columns.
if (auto layout = pendingFormItems.at(0).layout)
formLayout->addRow(layout);
else if (auto widget = pendingFormItems.at(0).widget)
formLayout->addRow(widget);
} else if (pendingFormItems.size() == 2) { // Normal case, both columns used.
if (auto label = pendingFormItems.at(0).widget) {
if (auto layout = pendingFormItems.at(1).layout)
formLayout->addRow(label, layout);
else if (auto widget = pendingFormItems.at(1).widget)
formLayout->addRow(label, widget);
} else {
if (auto layout = pendingFormItems.at(1).layout)
formLayout->addRow(pendingFormItems.at(0).text, layout);
else if (auto widget = pendingFormItems.at(1).widget)
formLayout->addRow(pendingFormItems.at(0).text, widget);
}
} else {
QTC_CHECK(false);
}
pendingFormItems.clear();
}
static void doLayoutHelper(QLayout *layout,
const LayoutBuilder::LayoutItems &items,
int currentGridRow = 0)
{
int currentGridColumn = 0;
LayoutBuilder::LayoutItems pendingFormItems;
auto formLayout = qobject_cast<QFormLayout *>(layout);
auto gridLayout = qobject_cast<QGridLayout *>(layout);
auto boxLayout = qobject_cast<QBoxLayout *>(layout);
for (const LayoutBuilder::LayoutItem &item : items) {
if (item.specialType == LayoutBuilder::SpecialType::Break) {
if (formLayout)
flushPendingFormItems(formLayout, pendingFormItems);
else if (gridLayout) {
if (currentGridColumn != 0) {
++currentGridRow;
currentGridColumn = 0;
}
}
continue;
}
QWidget *widget = item.widget;
if (gridLayout) {
Qt::Alignment align;
if (item.align == LayoutBuilder::AlignmentType::AlignAsFormLabel)
align = Qt::Alignment(widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment));
if (widget)
gridLayout->addWidget(widget, currentGridRow, currentGridColumn, 1, item.span, align);
else if (item.layout)
gridLayout->addLayout(item.layout, currentGridRow, currentGridColumn, 1, item.span, align);
currentGridColumn += item.span;
} else if (boxLayout) {
if (widget) {
boxLayout->addWidget(widget);
} else if (item.layout) {
boxLayout->addLayout(item.layout);
} else if (item.specialType == LayoutBuilder::SpecialType::Stretch) {
boxLayout->addStretch(item.specialValue.toInt());
} else if (item.specialType == LayoutBuilder::SpecialType::Space) {
boxLayout->addSpacing(item.specialValue.toInt());
}
} else {
pendingFormItems.append(item);
}
}
if (formLayout)
flushPendingFormItems(formLayout, pendingFormItems);
}
/*!
Constructs a layout item from the contents of another LayoutBuilder
*/
LayoutBuilder::LayoutItem::LayoutItem(const LayoutBuilder &builder, int span, Alignment align)
: widget(builder.parentWidget()), span(span), align(align)
{}
LayoutBuilder::LayoutItem::LayoutItem(const LayoutBuilder &builder)
{
layout = createLayoutFromType(builder.m_layoutType);
doLayoutHelper(layout, builder.m_items);
setMargins(builder.m_withMargins, layout);
}
/*!
\class Utils::LayoutBuilder::Space
@@ -145,81 +279,20 @@ LayoutBuilder::LayoutItem::LayoutItem(const LayoutBuilder &builder, int span, Al
\sa addItem(), addItems(), addRow(), finishRow()
*/
/*!
Constructs a new layout builder with the specified \a layoutType.
The constructed layout will be attached to the provided \c QWidget \a parent.
*/
LayoutBuilder::LayoutBuilder(QWidget *parent, LayoutType layoutType)
{
init(parent, layoutType);
}
LayoutBuilder::LayoutBuilder(LayoutType layoutType, const LayoutItems &items)
: m_layoutType(layoutType)
{
init(new QWidget, layoutType);
addItems(items);
m_items.reserve(items.size() * 2);
for (const LayoutItem &item : items)
addItem(item);
}
void LayoutBuilder::init(QWidget *parent, LayoutType layoutType)
{
switch (layoutType) {
case Form:
m_formLayout = new QFormLayout(parent);
m_formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
break;
case Grid:
m_gridLayout = new QGridLayout(parent);
break;
case HBox:
case HBoxWithMargins:
m_boxLayout = new QHBoxLayout(parent);
break;
case VBox:
case VBoxWithMargins:
m_boxLayout = new QVBoxLayout(parent);
break;
}
switch (layoutType) {
case Form:
case Grid:
case HBox:
case VBox:
layout()->setContentsMargins(0, 0, 0, 0);
break;
default:
break;
}
}
/*!
Constructs a new layout builder to extend an existing \a layout.
This constructor can be used to continue the work of previous layout building.
The type of the underlying layout and previous contents will be retained,
new items will be added below existing ones.
*/
LayoutBuilder::LayoutBuilder(QLayout *layout)
{
if (auto fl = qobject_cast<QFormLayout *>(layout)) {
m_formLayout = fl;
} else if (auto grid = qobject_cast<QGridLayout *>(layout)) {
m_gridLayout = grid;
m_currentGridRow = grid->rowCount();
m_currentGridColumn = 0;
}
}
LayoutBuilder::LayoutBuilder() = default;
/*!
Destructs a layout builder.
*/
LayoutBuilder::~LayoutBuilder()
{
if (m_formLayout)
flushPendingFormItems();
}
LayoutBuilder::~LayoutBuilder() = default;
/*!
Instructs a layout builder to finish the current row.
@@ -227,14 +300,7 @@ LayoutBuilder::~LayoutBuilder()
*/
LayoutBuilder &LayoutBuilder::finishRow()
{
if (m_formLayout)
flushPendingFormItems();
if (m_gridLayout) {
if (m_currentGridColumn != 0) {
++m_currentGridRow;
m_currentGridColumn = 0;
}
}
addItem(Break());
return *this;
}
@@ -260,182 +326,132 @@ LayoutBuilder &LayoutBuilder::addRow(const LayoutItems &items)
return finishRow().addItems(items);
}
/*!
\internal
*/
void LayoutBuilder::flushPendingFormItems()
{
QTC_ASSERT(m_formLayout, return);
if (m_pendingFormItems.isEmpty())
return;
// If there are more than two items, we cram the last ones in one hbox.
if (m_pendingFormItems.size() > 2) {
auto hbox = new QHBoxLayout;
hbox->setContentsMargins(0, 0, 0, 0);
for (int i = 1; i < m_pendingFormItems.size(); ++i) {
if (QWidget *w = m_pendingFormItems.at(i).widget)
hbox->addWidget(w);
else if (QLayout *l = m_pendingFormItems.at(i).layout)
hbox->addItem(l);
else
QTC_CHECK(false);
}
while (m_pendingFormItems.size() >= 2)
m_pendingFormItems.takeLast();
m_pendingFormItems.append(LayoutItem(hbox));
}
if (m_pendingFormItems.size() == 1) { // One one item given, so this spans both columns.
if (auto layout = m_pendingFormItems.at(0).layout)
m_formLayout->addRow(layout);
else if (auto widget = m_pendingFormItems.at(0).widget)
m_formLayout->addRow(widget);
} else if (m_pendingFormItems.size() == 2) { // Normal case, both columns used.
if (auto label = m_pendingFormItems.at(0).widget) {
if (auto layout = m_pendingFormItems.at(1).layout)
m_formLayout->addRow(label, layout);
else if (auto widget = m_pendingFormItems.at(1).widget)
m_formLayout->addRow(label, widget);
} else {
if (auto layout = m_pendingFormItems.at(1).layout)
m_formLayout->addRow(m_pendingFormItems.at(0).text, layout);
else if (auto widget = m_pendingFormItems.at(1).widget)
m_formLayout->addRow(m_pendingFormItems.at(0).text, widget);
}
} else {
QTC_CHECK(false);
}
m_pendingFormItems.clear();
}
/*!
Returns the layout this layout builder operates on.
*/
QLayout *LayoutBuilder::layout() const
{
if (m_formLayout)
return m_formLayout;
if (m_boxLayout)
return m_boxLayout;
return m_gridLayout;
}
QWidget *LayoutBuilder::parentWidget() const
{
QLayout *l = layout();
return l ? l->parentWidget() : nullptr;
}
/*!
Adds the layout item \a item to the current row.
*/
LayoutBuilder &LayoutBuilder::addItem(const LayoutItem &item)
{
if (item.widget && !item.widget->parent())
item.widget->setParent(layout()->parentWidget());
if (item.aspect) {
if (item.aspect)
item.aspect->addToLayout(*this);
} else {
if (m_gridLayout) {
if (auto widget = item.widget) {
Qt::Alignment align;
if (item.align == AlignAsFormLabel)
align = Qt::Alignment(widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment));
m_gridLayout->addWidget(widget, m_currentGridRow, m_currentGridColumn, 1, item.span, align);
}
m_currentGridColumn += item.span;
if (item.specialType == SpecialType::Break)
finishRow();
} else if (m_boxLayout) {
if (auto widget = item.widget) {
m_boxLayout->addWidget(widget);
} else if (item.specialType == SpecialType::Stretch) {
m_boxLayout->addStretch(item.specialValue.toInt());
} else if (item.specialType == SpecialType::Space) {
m_boxLayout->addSpacing(item.specialValue.toInt());
}
} else {
m_pendingFormItems.append(item);
}
}
else
m_items.push_back(item);
return *this;
}
void LayoutBuilder::doLayout(QWidget *parent)
{
QLayout *layout = createLayoutFromType(m_layoutType);
parent->setLayout(layout);
doLayoutHelper(layout, m_items);
setMargins(m_withMargins, layout);
}
/*!
Adds the layout item \a items to the current row.
*/
LayoutBuilder &LayoutBuilder::addItems(const QList<LayoutBuilder::LayoutItem> &items)
LayoutBuilder &LayoutBuilder::addItems(const LayoutItems &items)
{
for (const LayoutItem &item : items)
addItem(item);
return *this;
}
void LayoutBuilder::attachTo(QWidget *w, bool stretchAtBottom)
/*!
Attach the constructed layout to the provided \c QWidget \a parent.
This operation can only be performed once per LayoutBuilder instance.
*/
void LayoutBuilder::attachTo(QWidget *w, bool withMargins)
{
LayoutBuilder builder(w, VBoxWithMargins);
builder.addItem(*this);
if (stretchAtBottom)
builder.addItem(Stretch());
m_withMargins = withMargins;
doLayout(w);
}
QWidget *LayoutBuilder::emerge(bool withMargins)
{
m_withMargins = withMargins;
auto w = new QWidget;
doLayout(w);
return w;
}
/*!
Constructs a layout extender to extend an existing \a layout.
This constructor can be used to continue the work of previous layout building.
The type of the underlying layout and previous contents will be retained,
new items will be added below existing ones.
*/
LayoutExtender::LayoutExtender(QLayout *layout)
: m_layout(layout)
{}
LayoutExtender::~LayoutExtender()
{
QTC_ASSERT(m_layout, return);
int currentGridRow = 0;
if (auto gridLayout = qobject_cast<QGridLayout *>(m_layout))
currentGridRow = gridLayout->rowCount();
doLayoutHelper(m_layout, m_items, currentGridRow);
}
// Special items
LayoutBuilder::Break::Break()
{
specialType = LayoutBuilder::SpecialType::Break;
specialType = SpecialType::Break;
}
LayoutBuilder::Stretch::Stretch(int stretch)
{
specialType = LayoutBuilder::SpecialType::Stretch;
specialType = SpecialType::Stretch;
specialValue = stretch;
}
LayoutBuilder::Space::Space(int space)
{
specialType = LayoutBuilder::SpecialType::Space;
specialType = SpecialType::Space;
specialValue = space;
}
LayoutBuilder::Title::Title(const QString &title)
{
specialType = LayoutBuilder::SpecialType::Title;
specialType = SpecialType::Title;
specialValue = title;
}
// FIXME: Decide on which style to use:
// Group { Title(...), child1, child2, ...}; or
// Group { child1, child2, ... }.withTitle(...);
Layouting::Group &Layouting::Group::withTitle(const QString &title)
LayoutBuilder::Span::Span(int span_, const LayoutItem &item)
{
if (auto box = qobject_cast<QGroupBox *>(parentWidget()))
box->setTitle(title);
return *this;
LayoutBuilder::LayoutItem::operator=(item);
span = span_;
}
LayoutBuilder::AlignAsFormLabel::AlignAsFormLabel(const LayoutItem &item)
{
LayoutBuilder::LayoutItem::operator=(item);
align = AlignmentType::AlignAsFormLabel;
}
namespace Layouting {
Group::Group(std::initializer_list<LayoutItem> items)
: LayoutBuilder(new QGroupBox, VBoxWithMargins)
{
auto box = new QGroupBox;
Column builder;
bool innerMargins = true;
for (const LayoutItem &item : items) {
if (item.specialType == LayoutBuilder::SpecialType::Title) {
auto box = qobject_cast<QGroupBox *>(parentWidget());
QTC_ASSERT(box, continue);
box->setTitle(item.specialValue.toString());
box->setObjectName(item.specialValue.toString());
} else {
addItem(item);
builder.addItem(item);
}
}
builder.attachTo(box, innerMargins);
widget = box;
}
Box::Box(LayoutType type, const LayoutItems &items)
: LayoutBuilder(type, items)
{}
} // Layouting
} // Utils

View File

@@ -32,9 +32,6 @@
#include <QVariant>
QT_BEGIN_NAMESPACE
class QBoxLayout;
class QFormLayout;
class QGridLayout;
class QLayout;
class QWidget;
QT_END_NAMESPACE
@@ -47,53 +44,49 @@ class QTCREATOR_UTILS_EXPORT LayoutBuilder
{
public:
enum LayoutType {
Form, // Plain QFormLayout, without contentMargins
Grid, // Plain QGridLayout, without contentMargins
HBox, // Plain QHBoxLayout, without contentMargins
VBox, // Plain QVBoxLayout, without contentMargins
HBoxWithMargins, // QHBoxLayout with margins
VBoxWithMargins, // QVBoxLayout with margins
// Compat
FormLayout = Form, // FIXME: Remove
GridLayout = Grid, // FIXME: Remove
HBoxLayout,
VBoxLayout,
FormLayout,
GridLayout,
};
enum class AlignmentType {
DefaultAlignment,
AlignAsFormLabel,
};
enum Alignment { DefaultAlignment, AlignAsFormLabel };
enum class SpecialType {
NotSpecial,
Align,
Space,
Span,
Stretch,
Break,
Title
Title,
};
class QTCREATOR_UTILS_EXPORT LayoutItem
{
public:
LayoutItem();
LayoutItem(QLayout *layout, int span = 1, Alignment align = {});
LayoutItem(QWidget *widget, int span = 1, Alignment align = {});
LayoutItem(BaseAspect *aspect, int span = 1, Alignment align = {}); // Remove
LayoutItem(BaseAspect &aspect, int span = 1, Alignment align = {});
LayoutItem(const QString &text, int span = 1, Alignment align = {});
LayoutItem(const LayoutBuilder &builder, int span = 1, Alignment align = {});
LayoutItem(QLayout *layout);
LayoutItem(QWidget *widget);
LayoutItem(BaseAspect *aspect); // Remove
LayoutItem(BaseAspect &aspect);
LayoutItem(const QString &text);
LayoutItem(const LayoutBuilder &builder);
QLayout *layout = nullptr;
QWidget *widget = nullptr;
BaseAspect *aspect = nullptr;
QString text; // FIXME: Use specialValue for that
int span = 1;
Alignment align;
AlignmentType align = AlignmentType::DefaultAlignment;
SpecialType specialType = SpecialType::NotSpecial;
QVariant specialValue;
};
using LayoutItems = QList<LayoutItem>;
explicit LayoutBuilder(QWidget *parent, LayoutType layoutType = Form);
explicit LayoutBuilder(QLayout *layout); // Adds to existing layout.
explicit LayoutBuilder(LayoutType layoutType, const LayoutItems &items = {});
LayoutBuilder(const LayoutBuilder &) = delete;
@@ -110,10 +103,10 @@ public:
LayoutBuilder &addRow(const LayoutItem &item);
LayoutBuilder &addRow(const LayoutItems &items);
QLayout *layout() const;
QWidget *parentWidget() const;
LayoutType layoutType() const { return m_layoutType; }
void attachTo(QWidget *w, bool stretchAtBottom = true);
void attachTo(QWidget *w, bool withMargins = true);
QWidget *emerge(bool withMargins = true);
class QTCREATOR_UTILS_EXPORT Space : public LayoutItem
{
@@ -121,6 +114,18 @@ public:
explicit Space(int space);
};
class QTCREATOR_UTILS_EXPORT Span : public LayoutItem
{
public:
Span(int span, const LayoutItem &item);
};
class QTCREATOR_UTILS_EXPORT AlignAsFormLabel : public LayoutItem
{
public:
AlignAsFormLabel(const LayoutItem &item);
};
class QTCREATOR_UTILS_EXPORT Stretch : public LayoutItem
{
public:
@@ -133,75 +138,72 @@ public:
Break();
};
class QTCREATOR_UTILS_EXPORT Title : public LayoutBuilder::LayoutItem
class QTCREATOR_UTILS_EXPORT Title : public LayoutItem
{
public:
explicit Title(const QString &title);
};
private:
void flushPendingFormItems();
void init(QWidget *parent, LayoutType layoutType);
protected:
explicit LayoutBuilder(); // Adds to existing layout.
QFormLayout *m_formLayout = nullptr;
QGridLayout *m_gridLayout = nullptr;
QBoxLayout *m_boxLayout = nullptr;
LayoutItems m_pendingFormItems;
int m_currentGridRow = 0;
int m_currentGridColumn = 0;
void doLayout(QWidget *parent);
LayoutItems m_items;
LayoutType m_layoutType;
bool m_withMargins = false;
};
class QTCREATOR_UTILS_EXPORT LayoutExtender : public LayoutBuilder
{
public:
explicit LayoutExtender(QLayout *layout);
~LayoutExtender();
private:
QLayout *m_layout = nullptr;
};
namespace Layouting {
class QTCREATOR_UTILS_EXPORT Group : public LayoutBuilder
class QTCREATOR_UTILS_EXPORT Group : public LayoutBuilder::LayoutItem
{
public:
Group(std::initializer_list<LayoutBuilder::LayoutItem> items);
Group &withTitle(const QString &title);
};
class QTCREATOR_UTILS_EXPORT Box : public LayoutBuilder
class QTCREATOR_UTILS_EXPORT Column : public LayoutBuilder
{
public:
Box(LayoutType type, const LayoutItems &items);
Column() : Column({}) {}
Column(std::initializer_list<LayoutItem> items) : LayoutBuilder(VBoxLayout, items) {}
};
class QTCREATOR_UTILS_EXPORT Column : public Box
class QTCREATOR_UTILS_EXPORT Row : public LayoutBuilder
{
public:
Column(std::initializer_list<LayoutItem> items)
: Box(VBox, items)
{}
Row() : Row({}) {}
Row(std::initializer_list<LayoutItem> items) : LayoutBuilder(HBoxLayout, items) {}
};
class QTCREATOR_UTILS_EXPORT Row : public Box
class QTCREATOR_UTILS_EXPORT Grid : public LayoutBuilder
{
public:
Row(std::initializer_list<LayoutItem> items)
: Box(HBox, items)
{}
Grid() : Grid({}) {}
Grid(std::initializer_list<LayoutItem> items) : LayoutBuilder(GridLayout, items) {}
};
class QTCREATOR_UTILS_EXPORT Grid : public Box
class QTCREATOR_UTILS_EXPORT Form : public LayoutBuilder
{
public:
Grid(std::initializer_list<LayoutItem> items)
: Box(GridLayout, items)
{}
Form() : Form({}) {}
Form(std::initializer_list<LayoutItem> items) : LayoutBuilder(FormLayout, items) {}
};
class QTCREATOR_UTILS_EXPORT Form : public Box
{
public:
Form(std::initializer_list<LayoutItem> items)
: Box(FormLayout, items)
{}
};
using Item = LayoutBuilder::LayoutItem;
using Stretch = LayoutBuilder::Stretch;
using Space = LayoutBuilder::Space;
using Span = LayoutBuilder::Span;
using AlignAsFormLabel = LayoutBuilder::AlignAsFormLabel;
using Break = LayoutBuilder::Break;
using Title = LayoutBuilder::Title;

View File

@@ -517,10 +517,11 @@ QWidget *AndroidDeployQtStep::createConfigWidget()
AndroidManager::installQASIPackage(target(), packagePath);
});
LayoutBuilder builder(widget);
Layouting::Form builder;
builder.addRow(m_uninstallPreviousPackage);
builder.addRow(resetDefaultDevices);
builder.addRow(installCustomApkButton);
builder.attachTo(widget);
return widget;
}

View File

@@ -185,10 +185,11 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc)
});
auto initialCMakeAspect = bc->aspect<InitialCMakeArgumentsAspect>();
auto aspectWidget = new QWidget;
LayoutBuilder aspectWidgetBuilder(aspectWidget);
Layouting::Form aspectWidgetBuilder;
buildDirAspect->addToLayout(aspectWidgetBuilder);
aspectWidgetBuilder.finishRow();
initialCMakeAspect->addToLayout(aspectWidgetBuilder);
aspectWidgetBuilder.attachTo(aspectWidget, false);
mainLayout->addWidget(aspectWidget, row, 0, 1, -1);
++row;
@@ -196,9 +197,9 @@ CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc)
connect(qmlDebugAspect, &QtSupport::QmlDebuggingAspect::changed, this, [this]() {
updateButtonState();
});
auto widget = new QWidget;
LayoutBuilder builder(widget);
Layouting::Form builder;
qmlDebugAspect->addToLayout(builder);
auto widget = builder.emerge();
mainLayout->addWidget(widget, row, 0, 1, -1);
++row;

View File

@@ -442,8 +442,6 @@ QString CMakeBuildStep::activeRunConfigTarget() const
QWidget *CMakeBuildStep::createConfigWidget()
{
auto widget = new QWidget;
auto updateDetails = [this] {
ProcessParameters param;
setupProcessParameters(&param);
@@ -453,10 +451,6 @@ QWidget *CMakeBuildStep::createConfigWidget()
setDisplayName(tr("Build", "ConfigWidget display name."));
LayoutBuilder builder(widget);
builder.addRow(m_cmakeArguments);
builder.addRow(m_toolArguments);
auto buildTargetsView = new QTreeView;
buildTargetsView->setMinimumHeight(200);
buildTargetsView->setModel(&m_buildTargetModel);
@@ -466,7 +460,11 @@ QWidget *CMakeBuildStep::createConfigWidget()
auto frame = ItemViewFind::createSearchableWrapper(buildTargetsView,
ItemViewFind::LightColored);
Layouting::Form builder;
builder.addRow(m_cmakeArguments);
builder.addRow(m_toolArguments);
builder.addRow({new QLabel(tr("Targets:")), frame});
auto widget = builder.emerge();
updateDetails();

View File

@@ -233,7 +233,9 @@ CdbOptionsPageWidget::CdbOptionsPageWidget()
Title(tr("Add Exceptions to Issues View")),
s.firstChanceExceptionTaskEntry,
s.secondChanceExceptionTaskEntry
}
},
Stretch()
}.attachTo(this);
}
@@ -287,8 +289,9 @@ CdbPathsPageWidget::CdbPathsPageWidget()
finish();
Column {
Group { Title(tr("Symbol Paths")), m_symbolPaths },
Group { Title(tr("Source Paths")), m_sourcePaths }
}.attachTo(this, false);
Group { Title(tr("Source Paths")), m_sourcePaths },
Stretch()
}.attachTo(this);
}
void CdbPathsPageWidget::apply()

View File

@@ -170,8 +170,7 @@ DebuggerRunConfigurationAspect::DebuggerRunConfigurationAspect(Target *target)
setDisplayName(tr("Debugger settings"));
setConfigWidgetCreator([this] {
QWidget *w = new QWidget;
LayoutBuilder builder(w);
Layouting::Form builder;
builder.addRow(m_cppAspect);
builder.addRow(m_qmlAspect);
builder.addRow(m_overrideStartupAspect);
@@ -179,8 +178,7 @@ DebuggerRunConfigurationAspect::DebuggerRunConfigurationAspect(Target *target)
static const QByteArray env = qgetenv("QTC_DEBUGGER_MULTIPROCESS");
if (env.toInt())
builder.addRow(m_multiProcessAspect);
return w;
return builder.emerge(false);
});
m_cppAspect = new DebuggerLanguageAspect;

View File

@@ -87,7 +87,8 @@ GdbOptionsPageWidget::GdbOptionsPageWidget()
Column commands {
Group { Title { tr("Additional Startup Commands") }, s.gdbStartupCommands },
Group { Title { tr("Additional Attach Commands") }, s.gdbPostAttachCommands }
Group { Title { tr("Additional Attach Commands") }, s.gdbPostAttachCommands },
Stretch()
};
Row { general, commands }.attachTo(this);
@@ -128,7 +129,7 @@ GdbOptionsPageWidget2::GdbOptionsPageWidget2()
using namespace Layouting;
DebuggerSettings &s = *debuggerSettings();
Group {
Group extended {
Title(GdbOptionsPage::tr("Extended")),
labelDangerous,
s.targetAsync,
@@ -138,7 +139,9 @@ GdbOptionsPageWidget2::GdbOptionsPageWidget2()
s.breakOnAbort,
s.enableReverseDebugging,
s.multiInferior,
}.attachTo(this);
};
Column { extended, Stretch() }.attachTo(this);
}
// The "Dangerous" options.

View File

@@ -435,23 +435,25 @@ QWidget *FakeVimOptionPage::widget()
s.useFakeVim,
Group {
Title(tr("Vim Behavior")),
bools,
ints,
strings
}.withTitle(tr("Vim Behavior")),
},
Group {
Title(tr("Plugin Emulation")),
s.emulateVimCommentary,
s.emulateReplaceWithRegister,
s.emulateArgTextObj,
s.emulateExchange,
s.emulateSurround
}.withTitle(tr("Plugin Emulation")),
},
Row { copyTextEditorSettings, setQtStyle, setPlainStyle, Stretch() },
Stretch()
}.attachTo(m_widget);
}.attachTo(m_widget, true);
connect(copyTextEditorSettings, &QAbstractButton::clicked,
this, &FakeVimOptionPage::copyTextEditorSettings);

View File

@@ -51,9 +51,10 @@ MesonBuildSettingsWidget::MesonBuildSettingsWidget(MesonBuildConfiguration *buil
ui->setupUi(this);
ui->container->setState(Utils::DetailsWidget::NoSummary);
ui->container->setWidget(ui->details);
LayoutBuilder buildDirWBuilder{ui->buildDirWidget};
auto buildDirAspect = buildCfg->buildDirectoryAspect();
buildDirAspect->addToLayout(buildDirWBuilder);
Layouting::Form buildDirWBuilder;
buildCfg->buildDirectoryAspect()->addToLayout(buildDirWBuilder);
buildDirWBuilder.attachTo(ui->buildDirWidget);
ui->parametersLineEdit->setText(buildCfg->parameters());
ui->optionsFilterLineEdit->setFiltering(true);

View File

@@ -102,17 +102,17 @@ NimbleTaskStep::NimbleTaskStep(BuildStepList *parentList, Id id)
QWidget *NimbleTaskStep::createConfigWidget()
{
auto widget = new QWidget;
auto taskList = new QListView(widget);
auto taskList = new QListView;
taskList->setFrameShape(QFrame::StyledPanel);
taskList->setSelectionMode(QAbstractItemView::NoSelection);
taskList->setSelectionBehavior(QAbstractItemView::SelectRows);
taskList->setModel(&m_tasks);
LayoutBuilder builder(widget);
builder.addRow(m_taskArgs);
builder.addRow({tr("Tasks:"), taskList});
using namespace Layouting;
auto widget = Form {
m_taskArgs, Break(),
tr("Tasks:"), taskList
}.emerge(false);
auto buildSystem = dynamic_cast<NimbleBuildSystem *>(this->buildSystem());
QTC_ASSERT(buildSystem, return widget);

View File

@@ -111,7 +111,7 @@ void BuildDirectoryAspect::addToLayout(LayoutBuilder &builder)
builder.addRow({{}, d->problemLabel.data()});
updateProblemLabel();
if (!d->sourceDir.isEmpty()) {
connect(this, &StringAspect::checkedChanged, builder.layout(), [this] {
connect(this, &StringAspect::checkedChanged, this, [this] {
if (isChecked()) {
setFilePath(d->savedShadowBuildDir.isEmpty()
? d->sourceDir : d->savedShadowBuildDir);

View File

@@ -326,11 +326,12 @@ NamedWidget *BuildConfiguration::createConfigWidget()
widget = named;
}
LayoutBuilder builder(widget);
Layouting::Form builder;
for (BaseAspect *aspect : aspects()) {
if (aspect->isVisible())
aspect->addToLayout(builder.finishRow());
}
builder.attachTo(widget, false);
return named;
}

View File

@@ -179,13 +179,12 @@ QWidget *BuildStep::doCreateConfigWidget()
QWidget *BuildStep::createConfigWidget()
{
auto widget = new QWidget;
LayoutBuilder builder(widget);
Layouting::Form builder;
for (BaseAspect *aspect : qAsConst(m_aspects)) {
if (aspect->isVisible())
aspect->addToLayout(builder.finishRow());
}
auto widget = builder.emerge(false);
if (m_addMacroExpander)
VariableChooser::addSupportForChildWidgets(widget, macroExpander());

View File

@@ -737,7 +737,7 @@ void KitAspectWidget::addToLayout(LayoutBuilder &builder)
m_label = new QLabel(m_kitInformation->displayName() + ':');
m_label->setToolTip(m_kitInformation->description());
builder.addRow({{m_label, 1, LayoutBuilder::AlignAsFormLabel}, mainWidget(), buttonWidget()});
builder.addRow({LayoutBuilder::AlignAsFormLabel(m_label), mainWidget(), buttonWidget()});
}
void KitAspectWidget::setVisible(bool visible)

View File

@@ -68,10 +68,8 @@ KitManagerConfigWidget::KitManagerConfigWidget(Kit *k) :
{
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
LayoutBuilder builder(this, LayoutBuilder::GridLayout);
QLabel *label = new QLabel(tr("Name:"));
label->setToolTip(tr("Kit name and icon."));
builder.addRow({{label, 1, LayoutBuilder::AlignAsFormLabel}, m_nameEdit, m_iconButton});
QString toolTip =
tr("<html><head/><body><p>The name of the kit suitable for generating "
@@ -85,10 +83,15 @@ KitManagerConfigWidget::KitManagerConfigWidget(Kit *k) :
label = new QLabel(tr("File system name:"));
label->setToolTip(toolTip);
builder.addRow({{label, 1, LayoutBuilder::AlignAsFormLabel}, m_fileSystemFriendlyNameLineEdit});
connect(m_fileSystemFriendlyNameLineEdit, &QLineEdit::textChanged,
this, &KitManagerConfigWidget::setFileSystemFriendlyName);
using namespace Layouting;
Grid {
AlignAsFormLabel(label), m_nameEdit, m_iconButton, Break(),
AlignAsFormLabel(label), m_fileSystemFriendlyNameLineEdit
}.attachTo(this);
m_iconButton->setToolTip(tr("Kit icon."));
auto setIconAction = new QAction(tr("Select Icon..."), this);
m_iconButton->addAction(setIconAction);
@@ -230,7 +233,7 @@ void KitManagerConfigWidget::addAspectToWorkingCopy(KitAspect *aspect)
m_actions << action;
LayoutBuilder builder(layout());
LayoutExtender builder(layout());
widget->addToLayout(builder);
m_widgets.append(widget);
}

View File

@@ -346,9 +346,7 @@ CommandLine MakeStep::effectiveMakeCommand(MakeCommandType type) const
QWidget *MakeStep::createConfigWidget()
{
auto widget = new QWidget;
LayoutBuilder builder(widget);
Layouting::Form builder;
builder.addRow(m_makeCommandAspect);
builder.addRow(m_userArgumentsAspect);
builder.addRow({m_userJobCountAspect, m_overrideMakeflagsAspect, m_nonOverrideWarning});
@@ -356,6 +354,8 @@ QWidget *MakeStep::createConfigWidget()
builder.addRow(m_disabledForSubdirsAspect);
builder.addRow(m_buildTargetsAspect);
auto widget = builder.emerge(false);
VariableChooser::addSupportForChildWidgets(widget, macroExpander());
setSummaryUpdater([this] {

View File

@@ -215,15 +215,14 @@ bool RunConfiguration::isEnabled() const
QWidget *RunConfiguration::createConfigurationWidget()
{
auto widget = new QWidget;
{
LayoutBuilder builder(widget);
for (BaseAspect *aspect : qAsConst(m_aspects)) {
if (aspect->isVisible())
aspect->addToLayout(builder.finishRow());
}
Layouting::Form builder;
for (BaseAspect *aspect : qAsConst(m_aspects)) {
if (aspect->isVisible())
aspect->addToLayout(builder.finishRow());
}
auto widget = builder.emerge(false);
VariableChooser::addSupportForChildWidgets(widget, &m_expander);
auto detailsWidget = new Utils::DetailsWidget;

View File

@@ -129,8 +129,8 @@ void ArchitecturesAspect::addToLayout(LayoutBuilder &builder)
setVisibleDynamic(true);
};
connect(KitManager::instance(), &KitManager::kitsChanged, builder.layout(), changeHandler);
connect(this, &ArchitecturesAspect::changed, builder.layout(), changeHandler);
connect(KitManager::instance(), &KitManager::kitsChanged, this, changeHandler);
connect(this, &ArchitecturesAspect::changed, this, changeHandler);
changeHandler();
}
@@ -668,7 +668,7 @@ QbsBuildStepConfigWidget::QbsBuildStepConfigWidget(QbsBuildStep *step) :
installDirChooser = new PathChooser(this);
installDirChooser->setExpectedKind(PathChooser::Directory);
LayoutBuilder builder(this);
Layouting::Form builder;
builder.addRow(m_qbsStep->m_buildVariant);
builder.addRow(m_qbsStep->m_selectedAbis);
builder.addRow(m_qbsStep->m_maxJobCount);
@@ -686,6 +686,7 @@ QbsBuildStepConfigWidget::QbsBuildStepConfigWidget(QbsBuildStep *step) :
builder.addRow({tr("Installation directory:"), installDirChooser});
builder.addRow(m_qbsStep->m_commandLine);
builder.attachTo(this, false);
propertyEdit->setToolTip(tr("Properties to pass to the project."));
defaultInstallDirCheckBox->setText(tr("Use default location"));

View File

@@ -193,7 +193,7 @@ QWidget *QbsInstallStep::createConfigWidget()
commandLineTextEdit->setTextInteractionFlags(Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse);
commandLineTextEdit->setMinimumHeight(QFontMetrics(widget->font()).height() * 8);
LayoutBuilder builder(widget);
Layouting::Form builder;
builder.addRow({tr("Install root:"), installRootValueLabel});
builder.addRow(tr("Flags:"));
m_dryRun->addToLayout(builder);
@@ -201,6 +201,7 @@ QWidget *QbsInstallStep::createConfigWidget()
m_cleanInstallRoot->addToLayout(builder);
builder.addRow({commandLineKeyLabel, commandLineTextEdit});
builder.attachTo(widget);
const auto updateState = [this, commandLineTextEdit, installRootValueLabel] {
installRootValueLabel->setText(installRoot());

View File

@@ -511,18 +511,17 @@ bool QMakeStep::fromMap(const QVariantMap &map)
QWidget *QMakeStep::createConfigWidget()
{
auto widget = new QWidget;
abisLabel = new QLabel(tr("ABIs:"), widget);
abisLabel = new QLabel(tr("ABIs:"));
abisLabel->setAlignment(Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop);
abisListWidget = new QListWidget(widget);
abisListWidget = new QListWidget;
LayoutBuilder builder(widget);
Layouting::Form builder;
builder.addRow(m_buildType);
builder.addRow(m_userArgs);
builder.addRow(m_effectiveCall);
builder.addRow({abisLabel, abisListWidget});
auto widget = builder.emerge(false);
qmakeBuildConfigChanged();

View File

@@ -67,10 +67,11 @@ void QmlDebuggingAspect::addToLayout(LayoutBuilder &builder)
warningLabel->setText(warningText);
setVisible(supported);
const bool warningLabelsVisible = supported && !warningText.isEmpty();
warningLabel->setVisible(warningLabelsVisible);
if (!warningLabel->parentWidget())
warningLabel->setVisible(warningLabelsVisible);
};
connect(KitManager::instance(), &KitManager::kitsChanged, builder.layout(), changeHandler);
connect(this, &QmlDebuggingAspect::changed, builder.layout(), changeHandler);
connect(KitManager::instance(), &KitManager::kitsChanged, this, changeHandler);
connect(this, &QmlDebuggingAspect::changed, this, changeHandler);
changeHandler();
}
@@ -102,11 +103,11 @@ void QtQuickCompilerAspect::addToLayout(LayoutBuilder &builder)
const bool warningLabelsVisible = supported && !warningText.isEmpty();
warningLabel->setVisible(warningLabelsVisible);
};
connect(KitManager::instance(), &KitManager::kitsChanged, builder.layout(), changeHandler);
connect(this, &QmlDebuggingAspect::changed, builder.layout(), changeHandler);
connect(this, &QtQuickCompilerAspect::changed, builder.layout(), changeHandler);
connect(KitManager::instance(), &KitManager::kitsChanged, this, changeHandler);
connect(this, &QmlDebuggingAspect::changed, this, changeHandler);
connect(this, &QtQuickCompilerAspect::changed, this, changeHandler);
if (m_qmlDebuggingAspect) {
connect(m_qmlDebuggingAspect, &QmlDebuggingAspect::changed, builder.layout(),
connect(m_qmlDebuggingAspect, &QmlDebuggingAspect::changed, this,
changeHandler);
}
changeHandler();

View File

@@ -88,14 +88,14 @@ ValgrindConfigWidget::ValgrindConfigWidget(ValgrindBaseSettings *settings)
s.minimumInclusiveCostRatio, nl,
s.visualizationMinimumInclusiveCostRatio, nl,
s.enableEventToolTips, nl,
Item {
Span {
2,
Group {
s.enableCacheSim,
s.enableBranchSim,
s.collectSystime,
s.collectBusEvents,
},
2 // Span.
}
}
};

View File

@@ -214,14 +214,12 @@ void SuppressionAspect::addToLayout(LayoutBuilder &builder)
connect(d->entryList->selectionModel(), &QItemSelectionModel::selectionChanged,
d, &SuppressionAspectPrivate::slotSuppressionSelectionChanged);
Group group {
Title(tr("Suppression files:")),
Row {
d->entryList.data(),
Column { d->addEntry.data(), d->removeEntry.data(), Stretch() }
}
builder.addItem(tr("Suppression files:"));
Row group {
d->entryList.data(),
Column { d->addEntry.data(), d->removeEntry.data(), Stretch() }
};
builder.addItem(Item { group, 2 });
builder.addItem(Span { 2, group });
}
void SuppressionAspect::fromMap(const QVariantMap &map)