From 971938421c66cfdabc19452f97205f130fdabb84 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 14 May 2024 10:33:01 +0200 Subject: [PATCH] Use LayoutBuilder V2 This puts the implementation introduced in acf1ecb47fdf into use, after significant simplifications in the class hierarchy. CRTP is not used anymore, and the new tag based dispatch is also used for Layout::addItem, effectively reducing the number of different code paths. The Lua based settings access is disabled for now. Change-Id: Idb6d1a25675378757c5267bdb630bcd4c1f52d34 Reviewed-by: Alessandro Portale --- src/libs/utils/aspects.cpp | 83 +- src/libs/utils/aspects.h | 53 +- src/libs/utils/layoutbuilder.cpp | 1305 ++++++++--------- src/libs/utils/layoutbuilder.h | 589 ++++++-- src/libs/utils/projectintropage.cpp | 3 +- src/plugins/autotest/ctest/ctesttool.cpp | 6 +- .../autotest/projectsettingswidget.cpp | 6 +- src/plugins/autotest/testsettingspage.cpp | 2 +- .../beautifier/clangformat/clangformat.cpp | 3 +- src/plugins/beautifier/generalsettings.cpp | 2 +- .../clangformatglobalconfigwidget.cpp | 1 + .../cmakebuildconfiguration.cpp | 2 +- .../cmakeprojectmanager/cmakebuildstep.cpp | 2 +- .../cmakeprojectmanager/cmakeformatter.cpp | 2 +- .../cmakeprojectmanager/cmakekitaspect.cpp | 6 +- .../compilerexploreraspects.cpp | 4 +- .../compilerexploreraspects.h | 2 +- .../compilerexplorereditor.cpp | 7 +- src/plugins/coreplugin/fancytabwidget.cpp | 8 +- src/plugins/coreplugin/loggingviewer.cpp | 4 +- src/plugins/coreplugin/outputpanemanager.cpp | 6 +- src/plugins/cppcheck/cppchecksettings.cpp | 2 +- src/plugins/cppcheck/cppchecksettings.h | 2 +- .../cppeditor/cppcodemodelinspectordialog.cpp | 4 +- src/plugins/debugger/commonoptionspage.cpp | 2 +- src/plugins/debugger/commonoptionspage.h | 2 +- src/plugins/debugger/debuggeritemmanager.cpp | 2 +- src/plugins/debugger/debuggerkitaspect.cpp | 2 +- .../debuggerrunconfigurationaspect.cpp | 2 +- .../debuggersourcepathmappingwidget.cpp | 2 +- .../extensionmanagerwidget.cpp | 8 +- .../extensionmanager/extensionsbrowser.cpp | 2 +- src/plugins/fakevim/fakevimactions.cpp | 8 +- src/plugins/git/gitsettings.cpp | 2 +- src/plugins/gitlab/gitlaboptionspage.cpp | 2 +- .../incredibuild/commandbuilderaspect.cpp | 2 +- .../incredibuild/commandbuilderaspect.h | 2 +- src/plugins/ios/iosrunconfiguration.cpp | 2 +- src/plugins/ios/iosrunconfiguration.h | 2 +- .../languageclient/languageclientsettings.cpp | 10 +- .../languageclient/languageclientsettings.h | 2 +- .../lualanguageclient/lualanguageclient.cpp | 9 +- src/plugins/lua/bindings/inheritance.h | 53 +- src/plugins/lua/bindings/layout.cpp | 378 +++-- src/plugins/lua/bindings/settings.cpp | 4 +- src/plugins/lualsp/lualsp/init.lua | 5 +- src/plugins/mcusupport/mcukitaspect.cpp | 2 +- .../mesonbuildconfiguration.cpp | 2 +- .../mesonprojectmanager/toolkitaspectwidget.h | 2 +- src/plugins/nim/settings/nimsettings.cpp | 2 +- src/plugins/perforce/perforcesettings.cpp | 2 +- src/plugins/projectexplorer/buildaspects.cpp | 2 +- src/plugins/projectexplorer/buildaspects.h | 2 +- .../projectexplorer/buildconfiguration.cpp | 4 +- src/plugins/projectexplorer/buildstep.cpp | 8 +- .../codestylesettingspropertiespage.cpp | 1 + .../devicesupport/devicesettingspage.cpp | 5 +- src/plugins/projectexplorer/kitaspects.cpp | 12 +- src/plugins/projectexplorer/kitmanager.cpp | 2 +- src/plugins/projectexplorer/kitmanager.h | 4 +- .../kitmanagerconfigwidget.cpp | 2 +- .../projectexplorer/kitmanagerconfigwidget.h | 2 +- src/plugins/projectexplorer/makestep.cpp | 2 +- .../miniprojecttargetselector.cpp | 5 +- .../projectexplorer/projectwelcomepage.cpp | 6 +- .../projectexplorer/runconfiguration.cpp | 4 +- .../runconfigurationaspects.cpp | 16 +- .../projectexplorer/runconfigurationaspects.h | 8 +- src/plugins/python/pythonkitaspect.cpp | 2 +- .../customqbspropertiesdialog.cpp | 2 +- .../qbsprojectmanager/qbsbuildstep.cpp | 2 +- src/plugins/qbsprojectmanager/qbsbuildstep.h | 2 +- .../qbsprojectmanager/qbskitaspect.cpp | 2 +- .../qbsprofilessettingspage.cpp | 4 +- .../qmakeprojectmanager/qmakekitaspect.cpp | 2 +- src/plugins/qmakeprojectmanager/qmakestep.cpp | 2 +- .../assetexporterplugin/assetexportdialog.cpp | 2 +- .../timelineeditor/timelineanimationform.cpp | 6 +- .../timelineeditor/timelineform.cpp | 4 +- .../qmljseditor/qmljseditingsettingspage.cpp | 1 + .../qmlprojectmanager/qmlmainfileaspect.cpp | 2 +- .../qmlprojectmanager/qmlmainfileaspect.h | 2 +- src/plugins/qtsupport/qtbuildaspects.cpp | 8 +- src/plugins/qtsupport/qtbuildaspects.h | 4 +- src/plugins/qtsupport/qtkitaspect.cpp | 2 +- src/plugins/remotelinux/sshdevicewizard.cpp | 1 + src/plugins/screenrecorder/cropandtrim.cpp | 12 +- src/plugins/screenrecorder/export.cpp | 2 +- src/plugins/screenrecorder/record.cpp | 3 +- .../screenrecorder/screenrecorderplugin.cpp | 3 +- .../scxmleditor/common/shapestoolbox.cpp | 2 +- src/plugins/scxmleditor/common/stateview.cpp | 2 +- src/plugins/squish/squishsettings.cpp | 8 +- src/plugins/subversion/subversionsettings.cpp | 2 +- src/plugins/valgrind/valgrindsettings.cpp | 2 +- src/plugins/valgrind/valgrindsettings.h | 2 +- src/plugins/vcsbase/commonvcssettings.cpp | 6 +- .../webassemblyrunconfiguration.cpp | 2 +- .../webassembly/webassemblysettings.cpp | 1 + src/plugins/welcome/welcomeplugin.cpp | 10 +- .../tst_manual_widgets_layoutbuilder.cpp | 8 +- .../uifonts/tst_manual_widgets_uifonts.cpp | 26 +- 102 files changed, 1599 insertions(+), 1244 deletions(-) diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 664f4773872..6b9823c1175 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -279,14 +280,14 @@ QLabel *BaseAspect::createLabel() return label; } -void BaseAspect::addLabeledItem(LayoutItem &parent, QWidget *widget) +void BaseAspect::addLabeledItem(Layout &parent, QWidget *widget) { if (QLabel *l = createLabel()) { l->setBuddy(widget); parent.addItem(l); - parent.addItem(Span(std::max(d->m_spanX - 1, 1), LayoutItem(widget))); + parent.addItem(Span(std::max(d->m_spanX - 1, 1), widget)); } else { - parent.addItem(LayoutItem(widget)); + parent.addItem(widget); } } @@ -532,21 +533,20 @@ AspectContainer *BaseAspect::container() const Adds the visual representation of this aspect to the layout with the specified \a parent using a layout builder. */ -void BaseAspect::addToLayout(LayoutItem &) +void BaseAspect::addToLayout(Layout &) { } -void createItem(Layouting::LayoutItem *item, const BaseAspect &aspect) +void addToLayout(Layouting::Layout *iface, BaseAspect &aspect) { - const_cast(aspect).addToLayout(*item); + aspect.addToLayout(*iface); } -void createItem(Layouting::LayoutItem *item, const BaseAspect *aspect) +void addToLayout(Layouting::Layout *item, BaseAspect *aspect) { - const_cast(aspect)->addToLayout(*item); + aspect->addToLayout(*item); } - /*! Updates this aspect's value from user-initiated changes in the widget. @@ -858,15 +858,15 @@ public: aspect->bufferToGui(); } - void addToLayoutFirst(LayoutItem &parent) + void addToLayoutFirst(Layout &parent) { if (m_checked && m_checkBoxPlacement == CheckBoxPlacement::Top) { m_checked->addToLayout(parent); - parent.addItem(br); + parent.flush(); } } - void addToLayoutLast(LayoutItem &parent) + void addToLayoutLast(Layout &parent) { if (m_checked && m_checkBoxPlacement == CheckBoxPlacement::Right) m_checked->addToLayout(parent); @@ -1140,7 +1140,7 @@ void StringAspect::setAutoApplyOnEditingFinished(bool applyOnEditingFinished) d->m_autoApplyOnEditingFinished = applyOnEditingFinished; } -void StringAspect::addToLayout(LayoutItem &parent) +void StringAspect::addToLayout(Layout &parent) { d->m_checkerImpl.addToLayoutFirst(parent); @@ -1551,7 +1551,7 @@ PathChooser *FilePathAspect::pathChooser() const return d->m_pathChooserDisplay.data(); } -void FilePathAspect::addToLayout(Layouting::LayoutItem &parent) +void FilePathAspect::addToLayout(Layouting::Layout &parent) { d->m_checkerImpl.addToLayoutFirst(parent); @@ -1769,7 +1769,7 @@ ColorAspect::ColorAspect(AspectContainer *container) ColorAspect::~ColorAspect() = default; -void ColorAspect::addToLayout(Layouting::LayoutItem &parent) +void ColorAspect::addToLayout(Layouting::Layout &parent) { QTC_CHECK(!d->m_colorButton); d->m_colorButton = createSubWidget(); @@ -1946,7 +1946,7 @@ BoolAspect::BoolAspect(AspectContainer *container) */ BoolAspect::~BoolAspect() = default; -void BoolAspect::addToLayoutHelper(Layouting::LayoutItem &parent, QAbstractButton *button) +void BoolAspect::addToLayoutHelper(Layouting::Layout &parent, QAbstractButton *button) { switch (d->m_labelPlacement) { case LabelPlacement::Compact: @@ -1955,7 +1955,7 @@ void BoolAspect::addToLayoutHelper(Layouting::LayoutItem &parent, QAbstractButto break; case LabelPlacement::AtCheckBox: button->setText(labelText()); - parent.addItem(empty()); + parent.addItem(empty); parent.addItem(button); break; case LabelPlacement::InExtraLabel: @@ -1973,20 +1973,18 @@ void BoolAspect::addToLayoutHelper(Layouting::LayoutItem &parent, QAbstractButto }); } -LayoutItem BoolAspect::adoptButton(QAbstractButton *button) +std::function BoolAspect::adoptButton(QAbstractButton *button) { - LayoutItem parent; - - addToLayoutHelper(parent, button); - - bufferToGui(); - return parent; + return [this, button](Layouting::Layout *layout) { + addToLayoutHelper(*layout, button); + bufferToGui(); + }; } /*! \reimp */ -void BoolAspect::addToLayout(Layouting::LayoutItem &parent) +void BoolAspect::addToLayout(Layouting::Layout &parent) { QCheckBox *checkBox = createSubWidget(); addToLayoutHelper(parent, checkBox); @@ -2092,7 +2090,7 @@ SelectionAspect::~SelectionAspect() = default; /*! \reimp */ -void SelectionAspect::addToLayout(Layouting::LayoutItem &parent) +void SelectionAspect::addToLayout(Layouting::Layout &parent) { QTC_CHECK(d->m_buttonGroup == nullptr); QTC_CHECK(!d->m_comboBox); @@ -2266,7 +2264,7 @@ MultiSelectionAspect::~MultiSelectionAspect() = default; /*! \reimp */ -void MultiSelectionAspect::addToLayout(LayoutItem &builder) +void MultiSelectionAspect::addToLayout(Layout &builder) { QTC_CHECK(d->m_listView == nullptr); if (d->m_allValues.isEmpty()) @@ -2375,7 +2373,7 @@ IntegerAspect::~IntegerAspect() = default; /*! \reimp */ -void IntegerAspect::addToLayout(Layouting::LayoutItem &parent) +void IntegerAspect::addToLayout(Layouting::Layout &parent) { QTC_CHECK(!d->m_spinBox); d->m_spinBox = createSubWidget(); @@ -2477,7 +2475,7 @@ DoubleAspect::~DoubleAspect() = default; /*! \reimp */ -void DoubleAspect::addToLayout(LayoutItem &builder) +void DoubleAspect::addToLayout(Layout &builder) { QTC_CHECK(!d->m_spinBox); d->m_spinBox = createSubWidget(); @@ -2634,7 +2632,7 @@ StringListAspect::~StringListAspect() = default; /*! \reimp */ -void StringListAspect::addToLayout(LayoutItem &parent) +void StringListAspect::addToLayout(Layout &parent) { Q_UNUSED(parent) // TODO - when needed. @@ -2710,7 +2708,7 @@ void FilePathListAspect::bufferToGui() d->undoable.setWithoutUndo(m_buffer); } -void FilePathListAspect::addToLayout(LayoutItem &parent) +void FilePathListAspect::addToLayout(Layout &parent) { d->undoable.setSilently(value()); @@ -2804,7 +2802,7 @@ IntegersAspect::~IntegersAspect() = default; /*! \reimp */ -void IntegersAspect::addToLayout(Layouting::LayoutItem &parent) +void IntegersAspect::addToLayout(Layouting::Layout &parent) { Q_UNUSED(parent) // TODO - when needed. @@ -2841,7 +2839,7 @@ TextDisplay::~TextDisplay() = default; /*! \reimp */ -void TextDisplay::addToLayout(LayoutItem &parent) +void TextDisplay::addToLayout(Layout &parent) { if (!d->m_label) { d->m_label = createSubWidget(d->m_message, d->m_type); @@ -2893,7 +2891,7 @@ public: QList m_items; // Both owned and non-owned. QList m_ownedItems; // Owned only. QStringList m_settingsGroup; - std::function m_layouter; + std::function m_layouter; }; AspectContainer::AspectContainer() @@ -2908,6 +2906,11 @@ AspectContainer::~AspectContainer() qDeleteAll(d->m_ownedItems); } +void AspectContainer::addToLayout(Layouting::Layout &parent) +{ + parent.addItem(layouter()()); +} + /*! \internal */ @@ -2945,12 +2948,12 @@ AspectContainer::const_iterator AspectContainer::end() const return d->m_items.cend(); } -void AspectContainer::setLayouter(const std::function &layouter) +void AspectContainer::setLayouter(const std::function &layouter) { d->m_layouter = layouter; } -std::function AspectContainer::layouter() const +std::function AspectContainer::layouter() const { return d->m_layouter; } @@ -3447,7 +3450,7 @@ private: int m_index; }; -void AspectList::addToLayout(Layouting::LayoutItem &parent) +void AspectList::addToLayout(Layouting::Layout &parent) { using namespace Layouting; @@ -3466,7 +3469,7 @@ void AspectList::addToLayout(Layouting::LayoutItem &parent) addItem(d->createItem()); }); - Column column{noMargin()}; + Column column{noMargin}; forEachItem([&column, this](const std::shared_ptr &item, int idx) { auto removeBtn = new IconButton; @@ -3557,7 +3560,7 @@ bool StringSelectionAspect::guiToBuffer() return oldBuffer != m_buffer; } -void StringSelectionAspect::addToLayout(Layouting::LayoutItem &parent) +void StringSelectionAspect::addToLayout(Layouting::Layout &parent) { QTC_ASSERT(m_fillCallback, return); @@ -3630,4 +3633,4 @@ void StringSelectionAspect::addToLayout(Layouting::LayoutItem &parent) return addLabeledItem(parent, comboBox); } -} // namespace Utils +} // Utils diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index a5c22bb8584..13c1c22e4fc 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -29,9 +29,7 @@ class QStandardItemModel; class QItemSelectionModel; QT_END_NAMESPACE -namespace Layouting { -class LayoutItem; -} +namespace Layouting { class Layout; } namespace Utils { @@ -64,6 +62,7 @@ class QTCREATOR_UTILS_EXPORT BaseAspect : public QObject public: BaseAspect(AspectContainer *container = nullptr); + BaseAspect(const BaseAspect &) = delete; ~BaseAspect() override; Id id() const; @@ -125,9 +124,7 @@ public: virtual void toMap(Store &map) const; virtual void toActiveMap(Store &map) const { toMap(map); } virtual void volatileToMap(Store &map) const; - - virtual void addToLayout(Layouting::LayoutItem &parent); - + virtual void addToLayout(Layouting::Layout &parent); virtual void readSettings(); virtual void writeSettings() const; @@ -223,7 +220,7 @@ protected: virtual void handleGuiChanged(); QLabel *createLabel(); - void addLabeledItem(Layouting::LayoutItem &parent, QWidget *widget); + void addLabeledItem(Layouting::Layout &parent, QWidget *widget); void setDataCreatorHelper(const DataCreator &creator) const; void setDataClonerHelper(const DataCloner &cloner) const; @@ -276,8 +273,8 @@ private: friend class Internal::CheckableAspectImplementation; }; -QTCREATOR_UTILS_EXPORT void createItem(Layouting::LayoutItem *item, const BaseAspect &aspect); -QTCREATOR_UTILS_EXPORT void createItem(Layouting::LayoutItem *item, const BaseAspect *aspect); +QTCREATOR_UTILS_EXPORT void addToLayout(Layouting::Layout *layout, BaseAspect *aspect); +QTCREATOR_UTILS_EXPORT void addToLayout(Layouting::Layout *layout, BaseAspect &aspect); template class @@ -439,7 +436,7 @@ public: BoolAspect(AspectContainer *container = nullptr); ~BoolAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; std::function groupChecker(); Utils::CheckableDecider askAgainCheckableDecider(); @@ -452,10 +449,10 @@ public: LabelPlacement labelPlacement = LabelPlacement::InExtraLabel); void setLabelPlacement(LabelPlacement labelPlacement); - Layouting::LayoutItem adoptButton(QAbstractButton *button); + std::function adoptButton(QAbstractButton *button); private: - void addToLayoutHelper(Layouting::LayoutItem &parent, QAbstractButton *button); + void addToLayoutHelper(Layouting::Layout &parent, QAbstractButton *button); void bufferToGui() override; bool guiToBuffer() override; @@ -504,7 +501,7 @@ public: ColorAspect(AspectContainer *container = nullptr); ~ColorAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; private: void bufferToGui() override; @@ -521,7 +518,7 @@ public: SelectionAspect(AspectContainer *container = nullptr); ~SelectionAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void finish() override; QString stringValue() const; @@ -569,7 +566,7 @@ public: MultiSelectionAspect(AspectContainer *container = nullptr); ~MultiSelectionAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; enum class DisplayStyle { ListView }; void setDisplayStyle(DisplayStyle style); @@ -596,7 +593,7 @@ public: StringAspect(AspectContainer *container = nullptr); ~StringAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; QString operator()() const { return expandedValue(); } QString expandedValue() const; @@ -703,7 +700,7 @@ public: PathChooser *pathChooser() const; // Avoid to use. - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void fromMap(const Utils::Store &map) override; void toMap(Utils::Store &map) const override; @@ -730,7 +727,7 @@ public: IntegerAspect(AspectContainer *container = nullptr); ~IntegerAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void setRange(qint64 min, qint64 max); void setLabel(const QString &label); // FIXME: Use setLabelText @@ -759,7 +756,7 @@ public: DoubleAspect(AspectContainer *container = nullptr); ~DoubleAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void setRange(double min, double max); void setPrefix(const QString &prefix); @@ -831,7 +828,7 @@ public: StringListAspect(AspectContainer *container = nullptr); ~StringListAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void appendValue(const QString &value, bool allowDuplicates = true); void removeValue(const QString &value); @@ -855,7 +852,7 @@ public: bool guiToBuffer() override; void bufferToGui() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void setPlaceHolderText(const QString &placeHolderText); void appendValue(const FilePath &path, bool allowDuplicates = true); @@ -875,7 +872,7 @@ public: IntegersAspect(AspectContainer *container = nullptr); ~IntegersAspect() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; }; class QTCREATOR_UTILS_EXPORT TextDisplay : public BaseAspect @@ -888,7 +885,7 @@ public: InfoLabel::InfoType type = InfoLabel::None); ~TextDisplay() override; - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; void setIconType(InfoLabel::InfoType t); void setText(const QString &message); @@ -939,6 +936,8 @@ public: AspectContainer(const AspectContainer &) = delete; AspectContainer &operator=(const AspectContainer &) = delete; + void addToLayout(Layouting::Layout &parent) override; + void registerAspect(BaseAspect *aspect, bool takeOwnership = false); void registerAspects(const AspectContainer &aspects); @@ -989,8 +988,8 @@ public: const_iterator begin() const; const_iterator end() const; - void setLayouter(const std::function &layouter); - std::function layouter() const; + void setLayouter(const std::function &layouter); + std::function layouter() const; signals: void applied(); @@ -1131,7 +1130,7 @@ public: QVariant volatileVariantValue() const override { return {}; } - void addToLayout(Layouting::LayoutItem &parent) override; + void addToLayout(Layouting::Layout &parent) override; private: std::unique_ptr d; @@ -1143,7 +1142,7 @@ class QTCREATOR_UTILS_EXPORT StringSelectionAspect : public Utils::TypedAspect items)>; using FillCallback = std::function; diff --git a/src/libs/utils/layoutbuilder.cpp b/src/libs/utils/layoutbuilder.cpp index 31c67e4d0f6..029b41c3111 100644 --- a/src/libs/utils/layoutbuilder.cpp +++ b/src/libs/utils/layoutbuilder.cpp @@ -3,7 +3,6 @@ #include "layoutbuilder.h" -#include #include #include #include @@ -29,10 +28,23 @@ namespace Layouting { #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 +template +typename X::Implementation *access(const X *x) { - Q_OBJECT + return static_cast(x->ptr); +} +template +void apply(X *x, std::initializer_list 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) @@ -181,29 +193,49 @@ private: \namespace Layouting \inmodule QtCreator - \brief The Layouting namespace contains classes for use with layout builders. + \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 - \brief The LayoutItem class represents widgets, layouts, and aggregate - items for use in conjunction with layout builders. - - Layout items are typically implicitly constructed when adding items to a - \c LayoutBuilder instance using \c LayoutBuilder::addItem() or - \c LayoutBuilder::addItems() and never stored in user code. + The LayoutItem class is used for intermediate results + while creating layouts with a concept of rows and spans, such + as Form and Grid. */ -/*! - Constructs a layout item instance representing an empty cell. - */ LayoutItem::LayoutItem() = default; LayoutItem::~LayoutItem() = default; +LayoutItem::LayoutItem(const LayoutModifier &inner) +{ + ownerModifier = inner; +} /*! \fn template LayoutItem(const T &t) @@ -217,44 +249,15 @@ LayoutItem::~LayoutItem() = default; \li \c {QWidget *} \li \c {QLayout *} \endlist - */ +*/ -struct ResultItem +// Object + +Object::Object(std::initializer_list ps) { - ResultItem() = default; - explicit ResultItem(QLayout *l) : layout(l), empty(!l) {} - explicit ResultItem(QWidget *w) : widget(w), empty(!w) {} - - QString text; - QLayout *layout = nullptr; - QWidget *widget = nullptr; - int space = -1; - int stretch = -1; - int span = 1; - bool empty = false; -}; - -struct Slice -{ - Slice() = default; - Slice(QLayout *l) : layout(l) {} - Slice(QWidget *w, bool isLayouting=false) : widget(w), isLayouting(isLayouting) {} - - QLayout *layout = nullptr; - QWidget *widget = nullptr; - - void flush(); - - // Grid-specific - int currentGridColumn = 0; - int currentGridRow = 0; - bool isFormAlignment = false; - bool isLayouting = false; - Qt::Alignment align = {}; // Can be changed to - - // Grid or Form - QList pendingItems; -}; + ptr = new Implementation; + apply(this, ps); +} static QWidget *widgetForItem(QLayoutItem *item) { @@ -262,12 +265,11 @@ static QWidget *widgetForItem(QLayoutItem *item) return w; if (item->spacerItem()) return nullptr; - QLayout *l = item->layout(); - if (!l) - return nullptr; - for (int i = 0, n = l->count(); i < n; ++i) { - if (QWidget *w = widgetForItem(l->itemAt(i))) - return w; + 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; } @@ -279,7 +281,7 @@ static QLabel *createLabel(const QString &text) return label; } -static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item) +static void addItemToBoxLayout(QBoxLayout *layout, const LayoutItem &item) { if (QWidget *w = item.widget) { layout->addWidget(w); @@ -287,8 +289,6 @@ static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item) layout->addLayout(l); } else if (item.stretch != -1) { layout->addStretch(item.stretch); - } else if (item.space != -1) { - layout->addSpacing(item.space); } else if (!item.text.isEmpty()) { layout->addWidget(createLabel(item.text)); } else if (item.empty) { @@ -298,7 +298,7 @@ static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item) } } -static void addItemToFlowLayout(FlowLayout *layout, const ResultItem &item) +static void addItemToFlowLayout(FlowLayout *layout, const LayoutItem &item) { if (QWidget *w = item.widget) { layout->addWidget(w); @@ -306,8 +306,6 @@ static void addItemToFlowLayout(FlowLayout *layout, const ResultItem &item) layout->addItem(l); // } else if (item.stretch != -1) { // layout->addStretch(item.stretch); -// } else if (item.space != -1) { -// layout->addSpacing(item.space); } else if (item.empty) { // Nothing to do, but no reason to warn, either } else if (!item.text.isEmpty()) { @@ -317,163 +315,6 @@ static void addItemToFlowLayout(FlowLayout *layout, const ResultItem &item) } } -void Slice::flush() -{ - if (pendingItems.empty()) - return; - - if (auto formLayout = qobject_cast(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)); - } - - 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(createLabel(f0.text), f1.layout); - else if (f1.widget) - formLayout->addRow(createLabel(f0.text), f1.widget); - } - } else { - QTC_CHECK(false); - } - - // 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(l->widget())) { - if (QWidget *widget = widgetForItem(f)) - label->setBuddy(widget); - } - } - - } else if (auto gridLayout = qobject_cast(layout)) { - - 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, a); - else if (!item.text.isEmpty()) - gridLayout->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, a); - currentGridColumn += item.span; - } - ++currentGridRow; - currentGridColumn = 0; - - } else if (auto boxLayout = qobject_cast(layout)) { - - for (const ResultItem &item : std::as_const(pendingItems)) - addItemToBoxLayout(boxLayout, item); - - } else if (auto flowLayout = qobject_cast(layout)) { - - for (const ResultItem &item : std::as_const(pendingItems)) - addItemToFlowLayout(flowLayout, item); - - } else { - QTC_CHECK(false); - } - - pendingItems.clear(); -} - -// LayoutBuilder - -class LayoutBuilder -{ - Q_DISABLE_COPY_MOVE(LayoutBuilder) - -public: - LayoutBuilder(); - ~LayoutBuilder(); - - void addItem(const LayoutItem &item); - void addItems(const LayoutItems &items); - - QList 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 Layouting::Space \inmodule QtCreator @@ -488,39 +329,67 @@ void doAddWidget(LayoutBuilder &builder, QWidget *widget) \brief The Stretch class represents some stretch in a layout. */ -/*! - \class Layouting::LayoutBuilder - \internal - \inmodule QtCreator - \brief The LayoutBuilder class provides a convenient way to fill \c QFormLayout - and \c QGridLayouts with contents. +// Layout - Filling a layout with items happens item-by-item, row-by-row. - - A LayoutBuilder instance is typically used locally within a function and never stored. - - \sa addItem(), addItems() -*/ - - -LayoutBuilder::LayoutBuilder() = default; - -/*! - \internal - Destructs a layout builder. - */ -LayoutBuilder::~LayoutBuilder() = default; - -void LayoutBuilder::addItem(const LayoutItem &item) +void Layout::span(int cols, int rows) { - addItemHelper(*this, item); + QTC_ASSERT(!pendingItems.empty(), return); + pendingItems.back().spanCols = cols; + pendingItems.back().spanRows = rows; } -void LayoutBuilder::addItems(const LayoutItems &items) +void Layout::noMargin() { - for (const LayoutItem &item : items) - addItemHelper(*this, item); + 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::addLayoutItem(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 items) +{ + for (const I &item : items) + item.apply(this); } /*! @@ -529,485 +398,527 @@ void LayoutBuilder::addItems(const LayoutItems &items) \sa addItem(), addItems() */ -void LayoutItem::addRow(const LayoutItems &items) + +void Layout::addRow(std::initializer_list items) { - addItem(br); - addItems(items); + for (const I &item : items) + item.apply(this); + flush(); } -/*! - Adds the layout item \a item as sub items. - */ -void LayoutItem::addItem(const LayoutItem &item) +void Layout::setSpacing(int spacing) { - subItems.append(item); + access(this)->setSpacing(spacing); } -/*! - Adds the layout items \a items as sub items. - */ -void LayoutItem::addItems(const LayoutItems &items) +void Layout::setColumnStretch(int column, int stretch) { - subItems.append(items); -} - -/*! - Attaches the constructed layout to the provided QWidget \a w. - - This operation can only be performed once per LayoutBuilder instance. - */ - -void LayoutItem::attachTo(QWidget *w) const -{ - LayoutBuilder builder; - - builder.stack.append(w); - addItemHelper(builder, *this); -} - -QWidget *LayoutItem::emerge() -{ - LayoutBuilder builder; - - builder.stack.append(Slice()); - addItemHelper(builder, *this); - - if (builder.stack.empty()) - return nullptr; - - QTC_ASSERT(builder.stack.last().pendingItems.size() == 1, return nullptr); - ResultItem ri = builder.stack.last().pendingItems.takeFirst(); - - QTC_ASSERT(ri.layout || ri.widget, return nullptr); - - if (ri.layout) { - auto w = new QWidget; - w->setLayout(ri.layout); - return w; + if (auto grid = qobject_cast(access(this))) { + grid->setColumnStretch(column, stretch); + } else { + QTC_CHECK(false); } - - return ri.widget; } -static void layoutExit(LayoutBuilder &builder) +void addToWidget(Widget *widget, const Layout &layout) { - builder.stack.last().flush(); - QLayout *layout = builder.stack.last().layout; - builder.stack.pop_back(); - - if (builder.stack.last().isLayouting) { - builder.stack.last().pendingItems.append(ResultItem(layout)); - } else if (QWidget *widget = builder.stack.last().widget) { - widget->setLayout(layout); - } else - builder.stack.last().pendingItems.append(ResultItem(layout)); + layout.flush_(); + access(widget)->setLayout(access(&layout)); } -template -static void layoutingWidgetExit(LayoutBuilder &builder) +void addToLayout(Layout *layout, const Widget &inner) { - const Slice slice = builder.stack.last(); - T *w = qobject_cast(slice.widget); - for (const ResultItem &ri : slice.pendingItems) { - if (ri.widget) { - w->addWidget(ri.widget); - } else if (ri.layout) { - auto child = new QWidget; - child->setLayout(ri.layout); - w->addWidget(child); + LayoutItem item; + item.widget = access(&inner); + layout->addLayoutItem(item); +} + +void addToLayout(Layout *layout, QWidget *inner) +{ + LayoutItem item; + item.widget = inner; + layout->addLayoutItem(item); +} + +void addToLayout(Layout *layout, QLayout *inner) +{ + LayoutItem item; + item.layout = inner; + layout->addLayoutItem(item); +} + +void addToLayout(Layout *layout, const Layout &inner) +{ + inner.flush_(); + LayoutItem item; + item.layout = access(&inner); + layout->addLayoutItem(item); +} + +void addToLayout(Layout *layout, const LayoutModifier &inner) +{ + inner(layout); +} + +void addToLayout(Layout *layout, const QString &inner) +{ + LayoutItem item; + item.text = inner; + layout->addLayoutItem(item); +} + +void empty(Layout *iface) +{ + LayoutItem item; + item.empty = true; + iface->addLayoutItem(item); +} + +void hr(Layout *layout) +{ + layout->addLayoutItem(createHr()); +} + +void br(Layout *iface) +{ + iface->flush(); +} + +void st(Layout *iface) +{ + LayoutItem item; + item.stretch = 1; + iface->addLayoutItem(item); +} + +void noMargin(Layout *iface) +{ + iface->noMargin(); +} + +void normalMargin(Layout *iface) +{ + iface->normalMargin(); +} + +QFormLayout *Layout::asForm() +{ + return qobject_cast(access(this)); +} + +QGridLayout *Layout::asGrid() +{ + return qobject_cast(access(this)); +} + +QBoxLayout *Layout::asBox() +{ + return qobject_cast(access(this)); +} + +FlowLayout *Layout::asFlow() +{ + return dynamic_cast(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; } - builder.stack.pop_back(); - builder.stack.last().pendingItems.append(ResultItem(w)); -} -static void widgetExit(LayoutBuilder &builder) -{ - QWidget *widget = builder.stack.last().widget; - builder.stack.pop_back(); - builder.stack.last().pendingItems.append(ResultItem(widget)); -} + 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); + } -Column::Column(std::initializer_list items) -{ - subItems = items; - onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QVBoxLayout); }; - onExit = layoutExit; -} + 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); -Row::Row(std::initializer_list items) -{ - subItems = items; - onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QHBoxLayout); }; - onExit = layoutExit; -} + // 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); + } -Flow::Flow(std::initializer_list items) -{ - subItems = items; - onAdd = [](LayoutBuilder &builder) { builder.stack.append(new FlowLayout); }; - onExit = layoutExit; -} - -Grid::Grid(std::initializer_list items) -{ - subItems = items; - onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QGridLayout); }; - onExit = layoutExit; -} - -static QFormLayout *newFormLayout() -{ - auto formLayout = new QFormLayout; - formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); - return formLayout; -} - -Form::Form(std::initializer_list items) -{ - subItems = items; - onAdd = [](LayoutBuilder &builder) { builder.stack.append(newFormLayout()); }; - onExit = layoutExit; -} - -LayoutItem br() -{ - LayoutItem item; - item.onAdd = [](LayoutBuilder &builder) { - builder.stack.last().flush(); - }; - return item; -} - -LayoutItem empty() -{ - LayoutItem item; - item.onAdd = [](LayoutBuilder &builder) { - ResultItem ri; - ri.empty = true; - builder.stack.last().pendingItems.append(ri); - }; - return item; -} - -LayoutItem hr() -{ - 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() -{ - return customMargin({}); -} - -LayoutItem normalMargin() -{ - return customMargin({9, 9, 9, 9}); -} - -LayoutItem customMargin(const QMargins &margin) -{ - LayoutItem item; - item.onAdd = [margin](LayoutBuilder &builder) { - if (auto layout = builder.stack.last().layout) - layout->setContentsMargins(margin); - else if (auto widget = builder.stack.last().widget) - widget->setContentsMargins(margin); - }; - 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; + // 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(l->widget())) { + if (QWidget *widget = widgetForItem(f)) + label->setBuddy(widget); } } - }; - return item; + + pendingItems.clear(); + return; + } + + QTC_CHECK(false); // The other layouts shouldn't use flush() +} + +void Layout::flush_() const +{ + const_cast(this)->flush(); +} + +void withFormAlignment(Layout *iface) +{ + iface->useFormAlignment = true; +} + +// Flow + +Flow::Flow(std::initializer_list ps) +{ + ptr = new FlowLayout; + apply(this, ps); + flush(); +} + +// Row & Column + +Row::Row(std::initializer_list ps) +{ + ptr = new QHBoxLayout; + apply(this, ps); + flush(); +} + +Column::Column(std::initializer_list ps) +{ + ptr = new QVBoxLayout; + apply(this, ps); + flush(); +} + +// Grid + +Grid::Grid() +{ + ptr = new QGridLayout; +} + +Grid::Grid(std::initializer_list ps) +{ + ptr = new QGridLayout; + apply(this, ps); + flush(); +} + +// Form + +Form::Form() +{ + ptr = new QFormLayout; +} + +Form::Form(std::initializer_list 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(this)->flush(); + QWidget *widget = new QWidget; + widget->setLayout(access(this)); + return widget; } // "Widgets" -template -void setupWidget(LayoutItem *item) +Widget::Widget(std::initializer_list ps) { - item->onAdd = [](LayoutBuilder &builder) { builder.stack.append(new T); }; - item->onExit = widgetExit; -}; - -Widget::Widget(std::initializer_list items) -{ - this->subItems = items; - setupWidget(this); + ptr = new Implementation; + apply(this, ps); } -Group::Group(std::initializer_list items) +void Widget::resize(int w, int h) { - this->subItems = items; - setupWidget(this); + access(this)->resize(w, h); } -Stack::Stack(std::initializer_list items) +void Widget::setLayout(const Layout &layout) { - // 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. - subItems = items; - onAdd = [](LayoutBuilder &builder) { - builder.stack.append(Slice(new QStackedWidget, true)); - }; - onExit = layoutingWidgetExit; + access(this)->setLayout(access(&layout)); } -PushButton::PushButton(std::initializer_list items) +void Widget::setWindowTitle(const QString &title) { - this->subItems = items; - setupWidget(this); + access(this)->setWindowTitle(title); } -SpinBox::SpinBox(std::initializer_list items) +void Widget::setToolTip(const QString &title) { - this->subItems = items; - setupWidget(this); + access(this)->setToolTip(title); } -TextEdit::TextEdit(std::initializer_list items) +void Widget::show() { - this->subItems = items; - setupWidget(this); + access(this)->show(); } -Splitter::Splitter(std::initializer_list items) +void Widget::noMargin(int) { - subItems = items; - onAdd = [](LayoutBuilder &builder) { - auto splitter = new QSplitter; - splitter->setOrientation(Qt::Vertical); - builder.stack.append(Slice(splitter, true)); - }; - onExit = layoutingWidgetExit; + customMargin({}); } -ToolBar::ToolBar(std::initializer_list items) +void Widget::normalMargin(int) { - subItems = items; - onAdd = [](LayoutBuilder &builder) { - auto toolbar = new QToolBar; - toolbar->setOrientation(Qt::Horizontal); - builder.stack.append(Slice(toolbar, true)); - }; - onExit = layoutingWidgetExit; + customMargin({9, 9, 9, 9}); } -TabWidget::TabWidget(std::initializer_list items) +void Widget::customMargin(const QMargins &margin) { - this->subItems = items; - setupWidget(this); + access(this)->setContentsMargins(margin); } -// Special Tab - -Tab::Tab(const QString &tabName, const LayoutItem &item) +QWidget *Widget::emerge() const { - 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(builder.stack.last().widget); - QTC_ASSERT(tabWidget, return); - tabWidget->addTab(inner, tabName); - }; + return access(this); +} + +// Label + +Label::Label(std::initializer_list 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 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 &checker) +{ + checker(access(this)); +} + +// SpinBox + +SpinBox::SpinBox(std::initializer_list ps) +{ + ptr = new Implementation; + apply(this, ps); +} + +void SpinBox::setValue(int val) +{ + access(this)->setValue(val); +} + +void SpinBox::onTextChanged(const std::function &func) +{ + QObject::connect(access(this), &QSpinBox::textChanged, func); +} + +// TextEdit + +TextEdit::TextEdit(std::initializer_list ps) +{ + ptr = new Implementation; + apply(this, ps); +} + +void TextEdit::setText(const QString &text) +{ + access(this)->setText(text); +} + +// PushButton + +PushButton::PushButton(std::initializer_list ps) +{ + ptr = new Implementation; + apply(this, ps); +} + +void PushButton::setText(const QString &text) +{ + access(this)->setText(text); +} + +void PushButton::onClicked(const std::function &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 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 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 ps) +{ + ptr = new Implementation; + apply(this, ps); + access(this)->setOrientation(Qt::Horizontal); +} + +// TabWidget + +TabWidget::TabWidget(std::initializer_list 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 LayoutItems &items, const LayoutItems &other) +If::If(bool condition, + const std::initializer_list ifcase, + const std::initializer_list thencase) + : used(condition ? ifcase : thencase) +{} + +void addToLayout(Layout *layout, const If &inner) { - subItems.append(condition ? items : other); + for (const Layout::I &item : inner.used) + item.apply(layout); } -// Special Application - -Application::Application(std::initializer_list items) -{ - subItems = items; - setupWidget(this); - onExit = {}; // Hack: Don't dropp the last slice, we need the resulting widget. -} - -int Application::exec(int &argc, char *argv[]) -{ - QApplication app(argc, argv); - LayoutBuilder builder; - addItemHelper(builder, *this); - if (QWidget *widget = builder.stack.last().widget) - widget->show(); - return app.exec(); -} - -// "Properties" - -LayoutItem title(const QString &title) -{ - return [title](QObject *target) { - if (auto groupBox = qobject_cast(target)) { - groupBox->setTitle(title); - groupBox->setObjectName(title); - } else if (auto widget = qobject_cast(target)) { - widget->setWindowTitle(title); - } else { - QTC_CHECK(false); - } - }; -} - -LayoutItem windowTitle(const QString &windowTitle) -{ - return [windowTitle](QObject *target) { - if (auto widget = qobject_cast(target)) { - widget->setWindowTitle(windowTitle); - } else { - QTC_CHECK(false); - } - }; -} - -LayoutItem text(const QString &text) -{ - return [text](QObject *target) { - if (auto button = qobject_cast(target)) { - button->setText(text); - } else if (auto textEdit = qobject_cast(target)) { - textEdit->setText(text); - } else { - QTC_CHECK(false); - } - }; -} - -LayoutItem tooltip(const QString &toolTip) -{ - return [toolTip](QObject *target) { - if (auto widget = qobject_cast(target)) { - widget->setToolTip(toolTip); - } else { - QTC_CHECK(false); - } - }; -} - -LayoutItem spacing(int spacing) -{ - return [spacing](QObject *target) { - if (auto layout = qobject_cast(target)) { - layout->setSpacing(spacing); - } else { - QTC_CHECK(false); - } - }; -} - -LayoutItem resize(int w, int h) -{ - return [w, h](QObject *target) { - if (auto widget = qobject_cast(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(target)) { - grid->setColumnStretch(column, stretch); - } else { - QTC_CHECK(false); - } - }; -} - -LayoutItem fieldGrowthPolicy(QFormLayout::FieldGrowthPolicy policy) -{ - return [policy](QObject *target) { - if (auto form = qobject_cast(target)) { - form->setFieldGrowthPolicy(policy); - } 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(id.ob)) - textEdit->setText(text); -} - -// Signals - -LayoutItem onClicked(const std::function &func, QObject *guard) -{ - return [func, guard](QObject *target) { - if (auto button = qobject_cast(target)) { - QObject::connect(button, &QAbstractButton::clicked, guard ? guard : target, func); - } else { - QTC_CHECK(false); - } - }; -} - -LayoutItem onTextChanged(const std::function &func, QObject *guard) -{ - return [func, guard](QObject *target) { - if (auto button = qobject_cast(target)) { - QObject::connect(button, &QSpinBox::textChanged, guard ? guard : target, func); - } else { - QTC_CHECK(false); - } - }; -} - -LayoutItem onValueChanged(const std::function &func, QObject *guard) -{ - return [func, guard](QObject *target) { - if (auto button = qobject_cast(target)) { - QObject::connect(button, &QSpinBox::valueChanged, guard ? guard : target, func); - } else { - QTC_CHECK(false); - } - }; -} - -// Convenience +// Specials QWidget *createHr(QWidget *parent) { @@ -1017,59 +928,49 @@ QWidget *createHr(QWidget *parent) return frame; } -// Singletons. +Span::Span(int cols, const Layout::I &item) + : item(item), spanCols(cols) +{} -LayoutItem::LayoutItem(const LayoutItem &t) +Span::Span(int cols, int rows, const Layout::I &item) + : item(item), spanCols(cols), spanRows(rows) +{} + +void addToLayout(Layout *layout, const Span &inner) { - operator=(t); + layout->addItem(inner.item); + if (layout->pendingItems.empty()) { + QTC_CHECK(inner.spanCols == 1 && inner.spanRows == 1); + return; + } + layout->pendingItems.back().spanCols = inner.spanCols; + layout->pendingItems.back().spanRows = inner.spanRows; } -void createItem(LayoutItem *item, LayoutItem(*t)()) +LayoutModifier spacing(int space) { - *item = t(); + return [space](Layout *iface) { iface->setSpacing(space); }; } -void createItem(LayoutItem *item, const std::function &t) +void addToLayout(Layout *layout, const Space &inner) { - item->setter = t; + if (auto lt = layout->asBox()) + lt->addSpacing(inner.space); } -void createItem(LayoutItem *item, QWidget *t) +void addToLayout(Layout *layout, const Stretch &inner) { - if (auto l = qobject_cast(t)) - l->setTextInteractionFlags(l->textInteractionFlags() | Qt::TextSelectableByMouse); - - item->onAdd = [t](LayoutBuilder &builder) { doAddWidget(builder, t); }; + if (auto lt = layout->asBox()) + lt->addStretch(inner.stretch); } -void createItem(LayoutItem *item, QLayout *t) -{ - item->onAdd = [t](LayoutBuilder &builder) { doAddLayout(builder, t); }; -} +// void createItem(LayoutItem *item, QWidget *t) +// { +// if (auto l = qobject_cast(t)) +// l->setTextInteractionFlags(l->textInteractionFlags() | Qt::TextSelectableByMouse); -void createItem(LayoutItem *item, const QString &t) -{ - item->onAdd = [t](LayoutBuilder &builder) { doAddText(builder, t); }; -} +// item->onAdd = [t](LayoutBuilder &builder) { doAddWidget(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" diff --git a/src/libs/utils/layoutbuilder.h b/src/libs/utils/layoutbuilder.h index c9fac7d8383..020ac32f9d0 100644 --- a/src/libs/utils/layoutbuilder.h +++ b/src/libs/utils/layoutbuilder.h @@ -3,12 +3,12 @@ #pragma once -#include -#include +#include #include -#include -#include +#include +#include +#include #if defined(UTILS_LIBRARY) # define QTCREATOR_UTILS_EXPORT Q_DECL_EXPORT @@ -19,251 +19,536 @@ #endif QT_BEGIN_NAMESPACE +class QBoxLayout; +class QFormLayout; +class QGridLayout; +class QGroupBox; +class QHBoxLayout; +class QLabel; class QLayout; class QMargins; class QObject; +class QPushButton; +class QSpinBox; +class QSplitter; +class QStackedWidget; +class QTabWidget; +class QTextEdit; +class QToolBar; +class QVBoxLayout; class QWidget; -template T qobject_cast(QObject *object); QT_END_NAMESPACE namespace Layouting { -// LayoutItem +class NestId {}; -class LayoutBuilder; -class LayoutItem; -using LayoutItems = QList; +template +class IdAndArg +{ +public: + IdAndArg(const T1 &id, const T2 &arg) : id(id), arg(arg) {} + const T1 id; + const T2 arg; // FIXME: Could be const &, but this would currently break bindTo(). +}; + +// The main dispatcher + +void doit(auto x, auto id, auto p); + +template class BuilderItem +{ +public: + // Nested child object + template + BuilderItem(Inner && p) + { + apply = [&p](X *x) { doit(x, NestId{}, std::forward(p)); }; + } + + // Property setter + template + BuilderItem(IdAndArg && idarg) + { + apply = [&idarg](X *x) { doit(x, idarg.id, idarg.arg); }; + } + + std::function apply; +}; + + +////////////////////////////////////////////// + +// +// Basic +// + +class QTCREATOR_UTILS_EXPORT Thing +{ +public: + void *ptr; // The product. +}; + +class QTCREATOR_UTILS_EXPORT Object : public Thing +{ +public: + using Implementation = QObject; + using I = BuilderItem; + + Object() = default; + Object(std::initializer_list ps); +}; + +// +// Layouts +// + +class FlowLayout; +class Layout; +using LayoutModifier = std::function; +// using LayoutModifier = void(*)(Layout *); class QTCREATOR_UTILS_EXPORT LayoutItem { public: - using Setter = std::function; - - LayoutItem(); ~LayoutItem(); + LayoutItem(); + LayoutItem(QLayout *l) : layout(l) {} + LayoutItem(QWidget *w) : widget(w) {} + LayoutItem(const QString &t) : text(t) {} + LayoutItem(const LayoutModifier &inner); - LayoutItem(const LayoutItem &t); - LayoutItem &operator=(const LayoutItem &t) = default; - - template LayoutItem(const T &t) - { - if constexpr (std::is_base_of_v) - LayoutItem::operator=(t); - else - createItem(this, t); - } - - void attachTo(QWidget *w) const; - QWidget *emerge(); - - void addItem(const LayoutItem &item); - void addItems(const LayoutItems &items); - void addRow(const LayoutItems &items); - - std::function onAdd; - std::function onExit; - std::function setter; - LayoutItems subItems; + QString text; + QLayout *layout = nullptr; + QWidget *widget = nullptr; + int stretch = -1; + int spanCols = 1; + int spanRows = 1; + bool empty = false; + LayoutModifier ownerModifier; + //Qt::Alignment align = {}; }; -// Special items - -class QTCREATOR_UTILS_EXPORT Space +class QTCREATOR_UTILS_EXPORT Layout : public Object { public: - explicit Space(int space) : space(space) {} - const int space; + using Implementation = QLayout; + using I = BuilderItem; + + Layout() = default; + Layout(Implementation *w) { ptr = w; } + + void span(int cols, int rows); + void noMargin(); + void normalMargin(); + void customMargin(const QMargins &margin); + void setColumnStretch(int cols, int rows); + void setSpacing(int space); + + void attachTo(QWidget *); + void addItem(I item); + void addItems(std::initializer_list items); + void addRow(std::initializer_list items); + void addLayoutItem(const LayoutItem &item); + + void flush(); + void flush_() const; + void fieldGrowthPolicy(int policy); + + QWidget *emerge() const; + + QFormLayout *asForm(); + QGridLayout *asGrid(); + QBoxLayout *asBox(); + FlowLayout *asFlow(); + + // Grid-only + int currentGridColumn = 0; + int currentGridRow = 0; + //Qt::Alignment align = {}; + bool useFormAlignment = false; + + std::vector pendingItems; +}; + +class QTCREATOR_UTILS_EXPORT Column : public Layout +{ +public: + using Implementation = QVBoxLayout; + using I = BuilderItem; + + Column(std::initializer_list ps); +}; + +class QTCREATOR_UTILS_EXPORT Row : public Layout +{ +public: + using Implementation = QHBoxLayout; + using I = BuilderItem; + + Row(std::initializer_list ps); +}; + +class QTCREATOR_UTILS_EXPORT Form : public Layout +{ +public: + using Implementation = QFormLayout; + using I = BuilderItem
; + + Form(); + Form(std::initializer_list ps); +}; + +class QTCREATOR_UTILS_EXPORT Grid : public Layout +{ +public: + using Implementation = QGridLayout; + using I = BuilderItem; + + Grid(); + Grid(std::initializer_list ps); +}; + +class QTCREATOR_UTILS_EXPORT Flow : public Layout +{ +public: + Flow(std::initializer_list ps); }; class QTCREATOR_UTILS_EXPORT Stretch { public: - explicit Stretch(int stretch = 1) : stretch(stretch) {} - const int stretch; + explicit Stretch(int stretch) : stretch(stretch) {} + + int stretch; +}; + +class QTCREATOR_UTILS_EXPORT Space +{ +public: + explicit Space(int space) : space(space) {} + + int space; }; class QTCREATOR_UTILS_EXPORT Span { public: - Span(int span, const LayoutItem &item) : span(span), item(item) {} - const int span; - LayoutItem item; + Span(int cols, const Layout::I &item); + Span(int cols, int rows, const Layout::I &item); + + Layout::I item; + int spanCols = 1; + int spanRows = 1; }; -class QTCREATOR_UTILS_EXPORT Column : public LayoutItem +// +// Widgets +// + +class QTCREATOR_UTILS_EXPORT Widget : public Object { public: - Column(std::initializer_list items); + using Implementation = QWidget; + using I = BuilderItem; + + Widget() = default; + Widget(std::initializer_list ps); + Widget(Implementation *w) { ptr = w; } + + QWidget *emerge() const; + + void show(); + void resize(int, int); + void setLayout(const Layout &layout); + void setWindowTitle(const QString &); + void setToolTip(const QString &); + void noMargin(int = 0); + void normalMargin(int = 0); + void customMargin(const QMargins &margin); }; -class QTCREATOR_UTILS_EXPORT Row : public LayoutItem +class QTCREATOR_UTILS_EXPORT Label : public Widget { public: - Row(std::initializer_list items); + using Implementation = QLabel; + using I = BuilderItem