diff options
Diffstat (limited to 'src/libs/utils/layoutbuilder.cpp')
-rw-r--r-- | src/libs/utils/layoutbuilder.cpp | 1061 |
1 files changed, 735 insertions, 326 deletions
diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index e7136c5a90..bea7faafd3 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -3,36 +3,188 @@ #include "layoutbuilder.h" -#include "aspects.h" -#include "qtcassert.h" - +#include <QDebug> #include <QFormLayout> #include <QGridLayout> #include <QGroupBox> +#include <QLabel> #include <QPushButton> #include <QStackedLayout> +#include <QSpacerItem> +#include <QSpinBox> #include <QSplitter> #include <QStyle> #include <QTabWidget> -#include <QWidget> +#include <QTextEdit> +#include <QApplication> + +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) + +class FlowLayout final : public QLayout +{ + Q_OBJECT + +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(); + } + } -namespace Utils::Layouting { + QList<QLayoutItem *> itemList; + int m_hSpace; + int m_vSpace; +}; /*! - \enum Utils::LayoutBuilder::LayoutType + \namespace Layouting \inmodule QtCreator - The LayoutType enum describes the type of \c QLayout a layout builder - operates on. - - \value Form - \value Grid - \value HBox - \value VBox + \brief The Layouting namespace contains classes for use with layout builders. */ + /*! - \class Utils::LayoutBuilder::LayoutItem + \class Layouting::LayoutItem \inmodule QtCreator \brief The LayoutItem class represents widgets, layouts, and aggregate @@ -46,83 +198,59 @@ namespace Utils::Layouting { /*! Constructs a layout item instance representing an empty cell. */ -LayoutItem::LayoutItem() -{} +LayoutItem::LayoutItem() = default; +LayoutItem::~LayoutItem() = default; -/*! - Constructs a layout item proxy for \a layout. - */ -LayoutItem::LayoutItem(QLayout *layout) - : layout(layout) -{} /*! - Constructs a layout item proxy for \a widget. - */ -LayoutItem::LayoutItem(QWidget *widget) - : widget(widget) -{} + \fn template <class T> LayoutItem(const T &t) + \internal -/*! - Constructs a layout item representing a \c BaseAspect. - - This ultimately uses the \a aspect's \c addToLayout(LayoutBuilder &) function, - which in turn can add one or more layout items to the target layout. + Constructs a layout item proxy for \a t. - \sa BaseAspect::addToLayout() + T could be + \list + \li \c {QString} + \li \c {QWidget *} + \li \c {QLayout *} + \endlist */ -LayoutItem::LayoutItem(BaseAspect &aspect) - : aspect(&aspect) -{} -LayoutItem::LayoutItem(BaseAspect *aspect) - : aspect(aspect) -{} +struct ResultItem +{ + ResultItem() = default; + explicit ResultItem(QLayout *l) : layout(l) {} + explicit ResultItem(QWidget *w) : widget(w) {} -/*! - Constructs a layout item containing some static \a text. - */ -LayoutItem::LayoutItem(const QString &text) - : text(text) -{} + QString text; + QLayout *layout = nullptr; + QWidget *widget = nullptr; + int space = -1; + int stretch = -1; + int span = 1; +}; -QLayout *LayoutBuilder::createLayout() const +struct Slice { + Slice() = default; + Slice(QLayout *l) : layout(l) {} + Slice(QWidget *w) : widget(w) {} + QLayout *layout = nullptr; - switch (m_layoutType) { - case LayoutBuilder::FormLayout: { - auto formLayout = new QFormLayout; - formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); - layout = formLayout; - break; - } - case LayoutBuilder::GridLayout: { - auto gridLayout = new QGridLayout; - layout = gridLayout; - break; - } - case LayoutBuilder::HBoxLayout: { - auto hboxLayout = new QHBoxLayout; - layout = hboxLayout; - break; - } - case LayoutBuilder::VBoxLayout: { - auto vboxLayout = new QVBoxLayout; - layout = vboxLayout; - break; - } - case LayoutBuilder::StackLayout: { - auto stackLayout = new QStackedLayout; - layout = stackLayout; - break; - } - } - QTC_ASSERT(layout, return nullptr); - if (m_spacing) - layout->setSpacing(*m_spacing); - return layout; -} + QWidget *widget = nullptr; + + void flush(); + + // Grid-specific + int currentGridColumn = 0; + int currentGridRow = 0; + bool isFormAlignment = false; + Qt::Alignment align = {}; // Can be changed to + + // Grid or Form + QList<ResultItem> pendingItems; +}; static QWidget *widgetForItem(QLayoutItem *item) { @@ -147,18 +275,16 @@ static QLabel *createLabel(const QString &text) return label; } -static void addItemToBoxLayout(QBoxLayout *layout, const LayoutItem &item) +static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item) { if (QWidget *w = item.widget) { layout->addWidget(w); } else if (QLayout *l = item.layout) { layout->addLayout(l); - } else if (item.specialType == LayoutItem::SpecialType::Stretch) { - layout->addStretch(item.specialValue.toInt()); - } else if (item.specialType == LayoutItem::SpecialType::Space) { - layout->addSpacing(item.specialValue.toInt()); - } else if (item.specialType == LayoutItem::SpecialType::HorizontalRule) { - layout->addWidget(Layouting::createHr()); + } 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 { @@ -166,138 +292,205 @@ static void addItemToBoxLayout(QBoxLayout *layout, const LayoutItem &item) } } -static void flushPendingFormItems(QFormLayout *formLayout, - LayoutBuilder::LayoutItems &pendingFormItems) +static void addItemToFlowLayout(FlowLayout *layout, const ResultItem &item) { - 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; - hbox->setContentsMargins(0, 0, 0, 0); - for (int i = 1; i < pendingFormItems.size(); ++i) - addItemToBoxLayout(hbox, pendingFormItems.at(i)); - while (pendingFormItems.size() >= 2) - pendingFormItems.pop_back(); - pendingFormItems.append(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); - } + 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.text.isEmpty()) { + layout->addWidget(createLabel(item.text)); } else { QTC_CHECK(false); } +} + +void Slice::flush() +{ + if (pendingItems.empty()) + return; - // Set up label as buddy if possible. - const int lastRow = formLayout->rowCount() - 1; - QLayoutItem *l = formLayout->itemAt(lastRow, QFormLayout::LabelRole); - QLayoutItem *f = formLayout->itemAt(lastRow, QFormLayout::FieldRole); - if (l && f) { - if (QLabel *label = qobject_cast<QLabel *>(l->widget())) { - if (QWidget *widget = widgetForItem(f)) - label->setBuddy(widget); + if (auto formLayout = qobject_cast<QFormLayout *>(layout)) { + + // If there are more than two items, we cram the last ones in one hbox. + 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.append(ResultItem(hbox)); } - } - pendingFormItems.clear(); -} + if (pendingItems.size() == 1) { // One one item given, so this spans both columns. + const ResultItem &f0 = pendingItems.at(0); + if (auto layout = f0.layout) + formLayout->addRow(layout); + else if (auto widget = f0.widget) + formLayout->addRow(widget); + } else if (pendingItems.size() == 2) { // Normal case, both columns used. + ResultItem &f1 = pendingItems[1]; + const ResultItem &f0 = pendingItems.at(0); + if (!f1.widget && !f1.layout && !f1.text.isEmpty()) + f1.widget = createLabel(f1.text); + + if (f0.widget) { + if (f1.layout) + formLayout->addRow(f0.widget, f1.layout); + else if (f1.widget) + formLayout->addRow(f0.widget, f1.widget); + } else { + if (f1.layout) + formLayout->addRow(f0.text, f1.layout); + else if (f1.widget) + formLayout->addRow(f0.text, f1.widget); + } + } else { + QTC_CHECK(false); + } -static void doLayoutHelper(QLayout *layout, - const LayoutBuilder::LayoutItems &items, - const Layouting::AttachType attachType, - 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); - auto stackLayout = qobject_cast<QStackedLayout *>(layout); - - for (const LayoutItem &item : items) { - if (item.specialType == LayoutItem::SpecialType::Break) { - if (formLayout) - flushPendingFormItems(formLayout, pendingFormItems); - else if (gridLayout) { - if (currentGridColumn != 0) { - ++currentGridRow; - currentGridColumn = 0; - } + // Set up label as buddy if possible. + const int lastRow = formLayout->rowCount() - 1; + QLayoutItem *l = formLayout->itemAt(lastRow, QFormLayout::LabelRole); + QLayoutItem *f = formLayout->itemAt(lastRow, QFormLayout::FieldRole); + if (l && f) { + if (QLabel *label = qobject_cast<QLabel *>(l->widget())) { + if (QWidget *widget = widgetForItem(f)) + label->setBuddy(widget); } - continue; } - QWidget *widget = item.widget; + } else if (auto gridLayout = qobject_cast<QGridLayout *>(layout)) { - if (gridLayout) { - Qt::Alignment align = {}; - if (attachType == Layouting::WithFormAlignment && currentGridColumn == 0) - align = Qt::Alignment(widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment)); - if (widget) - gridLayout->addWidget(widget, currentGridRow, currentGridColumn, 1, item.span, align); + for (const ResultItem &item : std::as_const(pendingItems)) { + Qt::Alignment a = currentGridColumn == 0 ? align : Qt::Alignment(); + if (item.widget) + gridLayout->addWidget(item.widget, currentGridRow, currentGridColumn, 1, item.span, a); else if (item.layout) - gridLayout->addLayout(item.layout, currentGridRow, currentGridColumn, 1, item.span, align); + gridLayout->addLayout(item.layout, currentGridRow, currentGridColumn, 1, item.span, a); else if (!item.text.isEmpty()) - gridLayout->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, align); + gridLayout->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, a); currentGridColumn += item.span; - } else if (boxLayout) { + } + ++currentGridRow; + currentGridColumn = 0; + + } else if (auto boxLayout = qobject_cast<QBoxLayout *>(layout)) { + + for (const ResultItem &item : std::as_const(pendingItems)) addItemToBoxLayout(boxLayout, item); - } else if (stackLayout) { - stackLayout->addWidget(item.widget); - } else { - pendingFormItems.append(item); + + } else if (auto flowLayout = qobject_cast<FlowLayout *>(layout)) { + + for (const ResultItem &item : std::as_const(pendingItems)) + addItemToFlowLayout(flowLayout, item); + + } else if (auto stackLayout = qobject_cast<QStackedLayout *>(layout)) { + for (const ResultItem &item : std::as_const(pendingItems)) { + if (item.widget) + stackLayout->addWidget(item.widget); + else + QTC_CHECK(false); } + + } else { + QTC_CHECK(false); } - if (formLayout) - flushPendingFormItems(formLayout, pendingFormItems); + pendingItems.clear(); } +// LayoutBuilder -/*! - Constructs a layout item from the contents of another LayoutBuilder - */ -LayoutItem::LayoutItem(const LayoutBuilder &builder) +class LayoutBuilder { - layout = builder.createLayout(); - doLayoutHelper(layout, builder.m_items, Layouting::WithoutMargins); + Q_DISABLE_COPY_MOVE(LayoutBuilder) + +public: + LayoutBuilder(); + ~LayoutBuilder(); + + void addItem(const LayoutItem &item); + void addItems(const LayoutItems &items); + + QList<Slice> stack; +}; + +static void addItemHelper(LayoutBuilder &builder, const LayoutItem &item) +{ + if (item.onAdd) + item.onAdd(builder); + + if (item.setter) { + if (QWidget *widget = builder.stack.last().widget) + item.setter(widget); + else if (QLayout *layout = builder.stack.last().layout) + item.setter(layout); + else + QTC_CHECK(false); + } + + for (const LayoutItem &subItem : item.subItems) + addItemHelper(builder, subItem); + + if (item.onExit) + item.onExit(builder); } +void doAddText(LayoutBuilder &builder, const QString &text) +{ + ResultItem fi; + fi.text = text; + builder.stack.last().pendingItems.append(fi); +} + +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) +{ + ResultItem fi; + fi.stretch = stretch.stretch; + builder.stack.last().pendingItems.append(fi); +} + +void doAddLayout(LayoutBuilder &builder, QLayout *layout) +{ + builder.stack.last().pendingItems.append(ResultItem(layout)); +} + +void doAddWidget(LayoutBuilder &builder, QWidget *widget) +{ + builder.stack.last().pendingItems.append(ResultItem(widget)); +} + + /*! - \class Utils::LayoutBuilder::Space + \class Layouting::Space \inmodule QtCreator - \brief The LayoutBuilder::Space class represents some empty space in a layout. + \brief The Space class represents some empty space in a layout. */ /*! - \class Utils::LayoutBuilder::Stretch + \class Layouting::Stretch \inmodule QtCreator - \brief The LayoutBuilder::Stretch class represents some stretch in a layout. + \brief The Stretch class represents some stretch in a layout. */ /*! - \class Utils::LayoutBuilder + \class Layouting::LayoutBuilder + \internal \inmodule QtCreator \brief The LayoutBuilder class provides a convenient way to fill \c QFormLayout @@ -307,241 +500,409 @@ LayoutItem::LayoutItem(const LayoutBuilder &builder) A LayoutBuilder instance is typically used locally within a function and never stored. - \sa addItem(), addItems(), addRow(), finishRow() + \sa addItem(), addItems() */ -LayoutBuilder::LayoutBuilder(LayoutType layoutType, const LayoutItems &items) - : m_layoutType(layoutType) -{ - m_items.reserve(items.size() * 2); - for (const LayoutItem &item : items) - addItem(item); -} - -LayoutBuilder &LayoutBuilder::setSpacing(int spacing) -{ - m_spacing = spacing; - return *this; -} LayoutBuilder::LayoutBuilder() = default; /*! + \internal Destructs a layout builder. */ LayoutBuilder::~LayoutBuilder() = default; -/*! - Instructs a layout builder to finish the current row. - This is implicitly called by LayoutBuilder's destructor. - */ -LayoutBuilder &LayoutBuilder::finishRow() +void LayoutBuilder::addItem(const LayoutItem &item) { - addItem(Break()); - return *this; + addItemHelper(*this, item); } -/*! - This starts a new row containing the \a item. The row can be further extended by - other items using \c addItem() or \c addItems(). - - \sa finishRow(), addItem(), addItems() - */ -LayoutBuilder &LayoutBuilder::addRow(const LayoutItem &item) +void LayoutBuilder::addItems(const LayoutItems &items) { - return finishRow().addItem(item); + for (const LayoutItem &item : items) + addItemHelper(*this, item); } /*! - This starts a new row containing \a items. The row can be further extended by + Starts a new row containing \a items. The row can be further extended by other items using \c addItem() or \c addItems(). - \sa finishRow(), addItem(), addItems() + \sa addItem(), addItems() */ -LayoutBuilder &LayoutBuilder::addRow(const LayoutItems &items) +void LayoutItem::addRow(const LayoutItems &items) { - return finishRow().addItems(items); + addItem(br); + addItems(items); } /*! - Adds the layout item \a item to the current row. + Adds the layout item \a item as sub items. */ -LayoutBuilder &LayoutBuilder::addItem(const LayoutItem &item) +void LayoutItem::addItem(const LayoutItem &item) { - if (item.aspect) { - item.aspect->addToLayout(*this); - if (m_layoutType == FormLayout || m_layoutType == VBoxLayout) - finishRow(); - } else { - m_items.push_back(item); - } - return *this; -} - -void LayoutBuilder::doLayout(QWidget *parent, Layouting::AttachType attachType) const -{ - QLayout *layout = createLayout(); - parent->setLayout(layout); - - doLayoutHelper(layout, m_items, attachType); - if (attachType == Layouting::WithoutMargins) - layout->setContentsMargins(0, 0, 0, 0); + subItems.append(item); } /*! - Adds the layout item \a items to the current row. + Adds the layout items \a items as sub items. */ -LayoutBuilder &LayoutBuilder::addItems(const LayoutItems &items) +void LayoutItem::addItems(const LayoutItems &items) { - for (const LayoutItem &item : items) - addItem(item); - return *this; + subItems.append(items); } /*! - Attach the constructed layout to the provided \c QWidget \a parent. + Attaches the constructed layout to the provided QWidget \a w. This operation can only be performed once per LayoutBuilder instance. */ -void LayoutBuilder::attachTo(QWidget *w, Layouting::AttachType attachType) const + +void LayoutItem::attachTo(QWidget *w) const { - doLayout(w, attachType); + LayoutBuilder builder; + + builder.stack.append(w); + addItemHelper(builder, *this); } -QWidget *LayoutBuilder::emerge(Layouting::AttachType attachType) +QWidget *LayoutItem::emerge() { auto w = new QWidget; - doLayout(w, attachType); + attachTo(w); return w; } -/*! - Constructs a layout extender to extend an existing \a layout. +static void layoutExit(LayoutBuilder &builder) +{ + builder.stack.last().flush(); + QLayout *layout = builder.stack.last().layout; + builder.stack.pop_back(); - 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. - */ + if (QWidget *widget = builder.stack.last().widget) + widget->setLayout(layout); + else + builder.stack.last().pendingItems.append(ResultItem(layout)); +} -LayoutExtender::LayoutExtender(QLayout *layout, Layouting::AttachType attachType) - : m_layout(layout), m_attachType(attachType) -{} +static void widgetExit(LayoutBuilder &builder) +{ + QWidget *widget = builder.stack.last().widget; + builder.stack.pop_back(); + builder.stack.last().pendingItems.append(ResultItem(widget)); +} -LayoutExtender::~LayoutExtender() +Column::Column(std::initializer_list<LayoutItem> items) { - QTC_ASSERT(m_layout, return); - int currentGridRow = 0; - if (auto gridLayout = qobject_cast<QGridLayout *>(m_layout)) - currentGridRow = gridLayout->rowCount(); - doLayoutHelper(m_layout, m_items, m_attachType, currentGridRow); + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QVBoxLayout); }; + onExit = layoutExit; } -// Special items +Row::Row(std::initializer_list<LayoutItem> items) +{ + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QHBoxLayout); }; + onExit = layoutExit; +} -Break::Break() +Flow::Flow(std::initializer_list<LayoutItem> items) { - specialType = SpecialType::Break; + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new FlowLayout); }; + onExit = layoutExit; } -Stretch::Stretch(int stretch) +Grid::Grid(std::initializer_list<LayoutItem> items) { - specialType = SpecialType::Stretch; - specialValue = stretch; + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QGridLayout); }; + onExit = layoutExit; } -Space::Space(int space) +static QFormLayout *newFormLayout() { - specialType = SpecialType::Space; - specialValue = space; + auto formLayout = new QFormLayout; + formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + return formLayout; } -Span::Span(int span_, const LayoutItem &item) +Form::Form(std::initializer_list<LayoutItem> items) { - LayoutItem::operator=(item); - span = span_; + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(newFormLayout()); }; + onExit = layoutExit; } -Tab::Tab(const QString &tabName, const LayoutBuilder &item) +Stack::Stack(std::initializer_list<LayoutItem> items) { - text = tabName; - widget = new QWidget; - item.attachTo(widget); + subItems = items; + onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QStackedLayout); }; + onExit = layoutExit; } -HorizontalRule::HorizontalRule() +LayoutItem br() { - specialType = SpecialType::HorizontalRule; + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + builder.stack.last().flush(); + }; + return item; } -// "Widgets" +LayoutItem empty() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + ResultItem ri; + ri.span = 1; + builder.stack.last().pendingItems.append(ResultItem()); + }; + return item; +} -static void applyItems(QWidget *widget, const QList<LayoutItem> &items) +LayoutItem hr() { - bool hadLayout = false; - for (const LayoutItem &item : items) { - if (item.setter) { - item.setter(widget); - } else if (item.layout && !hadLayout) { - hadLayout = true; - widget->setLayout(item.layout); - } else { - QTC_CHECK(false); + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { doAddWidget(builder, createHr()); }; + return item; +} + +LayoutItem st() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { doAddStretch(builder, Stretch(1)); }; + return item; +} + +LayoutItem noMargin() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + if (auto layout = builder.stack.last().layout) + layout->setContentsMargins(0, 0, 0, 0); + else if (auto widget = builder.stack.last().widget) + widget->setContentsMargins(0, 0, 0, 0); + }; + return item; +} + +LayoutItem normalMargin() +{ + LayoutItem item; + item.onAdd = [](LayoutBuilder &builder) { + if (auto layout = builder.stack.last().layout) + layout->setContentsMargins(9, 9, 9, 9); + else if (auto widget = builder.stack.last().widget) + widget->setContentsMargins(9, 9, 9, 9); + }; + return item; +} + +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; +} + +// "Widgets" + +template <class T> +void setupWidget(LayoutItem *item) +{ + item->onAdd = [](LayoutBuilder &builder) { builder.stack.append(new T); }; + item->onExit = widgetExit; +}; + +Widget::Widget(std::initializer_list<LayoutItem> items) +{ + this->subItems = items; + setupWidget<QWidget>(this); } Group::Group(std::initializer_list<LayoutItem> items) { - widget = new QGroupBox; - applyItems(widget, items); + this->subItems = items; + setupWidget<QGroupBox>(this); } PushButton::PushButton(std::initializer_list<LayoutItem> items) { - widget = new QPushButton; - applyItems(widget, items); + this->subItems = items; + setupWidget<QPushButton>(this); +} + +SpinBox::SpinBox(std::initializer_list<LayoutItem> items) +{ + this->subItems = items; + setupWidget<QSpinBox>(this); +} + +TextEdit::TextEdit(std::initializer_list<LayoutItem> items) +{ + this->subItems = items; + setupWidget<QTextEdit>(this); } Splitter::Splitter(std::initializer_list<LayoutItem> items) - : Splitter(new QSplitter(Qt::Vertical), items) {} +{ + subItems = items; + onAdd = [](LayoutBuilder &builder) { + auto splitter = new QSplitter; + splitter->setOrientation(Qt::Vertical); + builder.stack.append(splitter); + }; + onExit = [](LayoutBuilder &builder) { + const Slice slice = builder.stack.last(); + QSplitter *splitter = qobject_cast<QSplitter *>(slice.widget); + for (const ResultItem &ri : slice.pendingItems) { + if (ri.widget) + splitter->addWidget(ri.widget); + } + builder.stack.pop_back(); + builder.stack.last().pendingItems.append(ResultItem(splitter)); + }; +} -Splitter::Splitter(QSplitter *splitter, std::initializer_list<LayoutItem> items) +TabWidget::TabWidget(std::initializer_list<LayoutItem> items) { - widget = splitter; - for (const LayoutItem &item : items) - splitter->addWidget(item.widget); + this->subItems = items; + setupWidget<QTabWidget>(this); +} + +// 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); + }; } -TabWidget::TabWidget(std::initializer_list<Tab> tabs) - : TabWidget(new QTabWidget, tabs) {} +// Special Application + +Application::Application(std::initializer_list<LayoutItem> items) +{ + subItems = items; + setupWidget<QWidget>(this); + onExit = {}; // Hack: Don't dropp the last slice, we need the resulting widget. +} -TabWidget::TabWidget(QTabWidget *tabWidget, std::initializer_list<Tab> tabs) +int Application::exec(int &argc, char *argv[]) { - widget = tabWidget; - for (const Tab &tab : tabs) - tabWidget->addTab(tab.widget, tab.text); + QApplication app(argc, argv); + LayoutBuilder builder; + addItemHelper(builder, *this); + if (QWidget *widget = builder.stack.last().widget) + widget->show(); + return app.exec(); } // "Properties" -LayoutItem::Setter title(const QString &title, BoolAspect *checker) +LayoutItem title(const QString &title) { - return [title, checker](QObject *target) { + return [title](QObject *target) { if (auto groupBox = qobject_cast<QGroupBox *>(target)) { groupBox->setTitle(title); groupBox->setObjectName(title); - if (checker) { - groupBox->setCheckable(true); - groupBox->setChecked(checker->value()); - checker->setHandlesGroup(groupBox); - } + } else if (auto widget = qobject_cast<QWidget *>(target)) { + widget->setWindowTitle(title); } else { QTC_CHECK(false); } }; } -LayoutItem::Setter onClicked(const std::function<void ()> &func, QObject *guard) +LayoutItem text(const QString &text) +{ + return [text](QObject *target) { + if (auto button = qobject_cast<QAbstractButton *>(target)) { + button->setText(text); + } else if (auto textEdit = qobject_cast<QTextEdit *>(target)) { + textEdit->setText(text); + } else { + QTC_CHECK(false); + } + }; +} + +LayoutItem tooltip(const QString &toolTip) +{ + return [toolTip](QObject *target) { + if (auto widget = qobject_cast<QWidget *>(target)) { + widget->setToolTip(toolTip); + } else { + QTC_CHECK(false); + } + }; +} + +LayoutItem spacing(int spacing) +{ + return [spacing](QObject *target) { + if (auto layout = qobject_cast<QLayout *>(target)) { + layout->setSpacing(spacing); + } else { + QTC_CHECK(false); + } + }; +} + +LayoutItem resize(int w, int h) +{ + return [w, h](QObject *target) { + if (auto widget = qobject_cast<QWidget *>(target)) { + widget->resize(w, h); + } 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); + } + }; +} + +// Id based setters + +LayoutItem id(ID &out) +{ + return [&out](QObject *target) { out.ob = target; }; +} + +void setText(ID id, const QString &text) +{ + if (auto textEdit = qobject_cast<QTextEdit *>(id.ob)) + textEdit->setText(text); +} + +// Signals + +LayoutItem onClicked(const std::function<void ()> &func, QObject *guard) { return [func, guard](QObject *target) { if (auto button = qobject_cast<QAbstractButton *>(target)) { @@ -552,28 +913,30 @@ LayoutItem::Setter onClicked(const std::function<void ()> &func, QObject *guard) }; } -LayoutItem::Setter text(const QString &text) +LayoutItem onTextChanged(const std::function<void (const QString &)> &func, QObject *guard) { - return [text](QObject *target) { - if (auto button = qobject_cast<QAbstractButton *>(target)) { - button->setText(text); + return [func, guard](QObject *target) { + if (auto button = qobject_cast<QSpinBox *>(target)) { + QObject::connect(button, &QSpinBox::textChanged, guard ? guard : target, func); } else { QTC_CHECK(false); } }; } -LayoutItem::Setter tooltip(const QString &toolTip) +LayoutItem onValueChanged(const std::function<void (int)> &func, QObject *guard) { - return [toolTip](QObject *target) { - if (auto widget = qobject_cast<QWidget *>(target)) { - widget->setToolTip(toolTip); + return [func, guard](QObject *target) { + if (auto button = qobject_cast<QSpinBox *>(target)) { + QObject::connect(button, &QSpinBox::valueChanged, guard ? guard : target, func); } else { QTC_CHECK(false); } }; } +// Convenience + QWidget *createHr(QWidget *parent) { auto frame = new QFrame(parent); @@ -583,9 +946,55 @@ QWidget *createHr(QWidget *parent) } // Singletons. -Break br; -Stretch st; -Space empty(0); -HorizontalRule hr; -} // Utils::Layouting +LayoutItem::LayoutItem(const LayoutItem &t) +{ + operator=(t); +} + +void createItem(LayoutItem *item, LayoutItem(*t)()) +{ + *item = t(); +} + +void createItem(LayoutItem *item, const std::function<void(QObject *target)> &t) +{ + item->setter = t; +} + +void createItem(LayoutItem *item, QWidget *t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddWidget(builder, t); }; +} + +void createItem(LayoutItem *item, QLayout *t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddLayout(builder, t); }; +} + +void createItem(LayoutItem *item, const QString &t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddText(builder, t); }; +} + +void createItem(LayoutItem *item, const Space &t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddSpace(builder, t); }; +} + +void createItem(LayoutItem *item, const Stretch &t) +{ + item->onAdd = [t](LayoutBuilder &builder) { doAddStretch(builder, t); }; +} + +void createItem(LayoutItem *item, const Span &t) +{ + item->onAdd = [t](LayoutBuilder &builder) { + addItemHelper(builder, t.item); + builder.stack.last().pendingItems.last().span = t.span; + }; +} + +} // Layouting + +#include "layoutbuilder.moc" |