layoutbuilder.cpp 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091
  1. // Copyright (C) 2020 The Qt Company Ltd.
  2. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
  3. #include "layoutbuilder.h"
  4. #include "qglobal.h"
  5. #include <QApplication>
  6. #include <QDebug>
  7. #include <QFormLayout>
  8. #include <QGridLayout>
  9. #include <QGroupBox>
  10. #include <QLabel>
  11. #include <QPushButton>
  12. #include <QSpacerItem>
  13. #include <QSpinBox>
  14. #include <QSplitter>
  15. #include <QStackedLayout>
  16. #include <QStackedWidget>
  17. #include <QStyle>
  18. #include <QTabWidget>
  19. #include <QTextEdit>
  20. #include <QToolBar>
  21. namespace Layouting {
  22. // That's cut down qtcassert.{c,h} to avoid the dependency.
  23. #define QTC_STRINGIFY_HELPER(x) #x
  24. #define QTC_STRINGIFY(x) QTC_STRINGIFY_HELPER(x)
  25. #define QTC_STRING(cond) \
  26. qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QTC_STRINGIFY(__LINE__))
  27. #define QTC_ASSERT(cond, action) \
  28. if (Q_LIKELY(cond)) { \
  29. } else { \
  30. QTC_STRING(#cond); \
  31. action; \
  32. } \
  33. do { \
  34. } while (0)
  35. #define QTC_CHECK(cond) \
  36. if (cond) { \
  37. } else { \
  38. QTC_STRING(#cond); \
  39. } \
  40. do { \
  41. } while (0)
  42. class FlowLayout final : public QLayout
  43. {
  44. Q_OBJECT
  45. public:
  46. explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1)
  47. : QLayout(parent)
  48. , m_hSpace(hSpacing)
  49. , m_vSpace(vSpacing)
  50. {
  51. setContentsMargins(margin, margin, margin, margin);
  52. }
  53. FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1)
  54. : m_hSpace(hSpacing)
  55. , m_vSpace(vSpacing)
  56. {
  57. setContentsMargins(margin, margin, margin, margin);
  58. }
  59. ~FlowLayout() override
  60. {
  61. QLayoutItem *item;
  62. while ((item = takeAt(0)))
  63. delete item;
  64. }
  65. void addItem(QLayoutItem *item) override { itemList.append(item); }
  66. int horizontalSpacing() const
  67. {
  68. if (m_hSpace >= 0)
  69. return m_hSpace;
  70. else
  71. return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
  72. }
  73. int verticalSpacing() const
  74. {
  75. if (m_vSpace >= 0)
  76. return m_vSpace;
  77. else
  78. return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
  79. }
  80. Qt::Orientations expandingDirections() const override { return {}; }
  81. bool hasHeightForWidth() const override { return true; }
  82. int heightForWidth(int width) const override
  83. {
  84. int height = doLayout(QRect(0, 0, width, 0), true);
  85. return height;
  86. }
  87. int count() const override { return itemList.size(); }
  88. QLayoutItem *itemAt(int index) const override { return itemList.value(index); }
  89. QSize minimumSize() const override
  90. {
  91. QSize size;
  92. for (QLayoutItem *item : itemList)
  93. size = size.expandedTo(item->minimumSize());
  94. int left, top, right, bottom;
  95. getContentsMargins(&left, &top, &right, &bottom);
  96. size += QSize(left + right, top + bottom);
  97. return size;
  98. }
  99. void setGeometry(const QRect &rect) override
  100. {
  101. QLayout::setGeometry(rect);
  102. doLayout(rect, false);
  103. }
  104. QSize sizeHint() const override { return minimumSize(); }
  105. QLayoutItem *takeAt(int index) override
  106. {
  107. if (index >= 0 && index < itemList.size())
  108. return itemList.takeAt(index);
  109. else
  110. return nullptr;
  111. }
  112. private:
  113. int doLayout(const QRect &rect, bool testOnly) const
  114. {
  115. int left, top, right, bottom;
  116. getContentsMargins(&left, &top, &right, &bottom);
  117. QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
  118. int x = effectiveRect.x();
  119. int y = effectiveRect.y();
  120. int lineHeight = 0;
  121. for (QLayoutItem *item : itemList) {
  122. QWidget *wid = item->widget();
  123. int spaceX = horizontalSpacing();
  124. if (spaceX == -1)
  125. spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton,
  126. QSizePolicy::PushButton,
  127. Qt::Horizontal);
  128. int spaceY = verticalSpacing();
  129. if (spaceY == -1)
  130. spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton,
  131. QSizePolicy::PushButton,
  132. Qt::Vertical);
  133. int nextX = x + item->sizeHint().width() + spaceX;
  134. if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
  135. x = effectiveRect.x();
  136. y = y + lineHeight + spaceY;
  137. nextX = x + item->sizeHint().width() + spaceX;
  138. lineHeight = 0;
  139. }
  140. if (!testOnly)
  141. item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
  142. x = nextX;
  143. lineHeight = qMax(lineHeight, item->sizeHint().height());
  144. }
  145. return y + lineHeight - rect.y() + bottom;
  146. }
  147. int smartSpacing(QStyle::PixelMetric pm) const
  148. {
  149. QObject *parent = this->parent();
  150. if (!parent) {
  151. return -1;
  152. } else if (parent->isWidgetType()) {
  153. auto pw = static_cast<QWidget *>(parent);
  154. return pw->style()->pixelMetric(pm, nullptr, pw);
  155. } else {
  156. return static_cast<QLayout *>(parent)->spacing();
  157. }
  158. }
  159. QList<QLayoutItem *> itemList;
  160. int m_hSpace;
  161. int m_vSpace;
  162. };
  163. /*!
  164. \namespace Layouting
  165. \inmodule QtCreator
  166. \brief The Layouting namespace contains classes for use with layout builders.
  167. */
  168. /*!
  169. \class Layouting::LayoutItem
  170. \inmodule QtCreator
  171. \brief The LayoutItem class represents widgets, layouts, and aggregate
  172. items for use in conjunction with layout builders.
  173. Layout items are typically implicitly constructed when adding items to a
  174. \c LayoutBuilder instance using \c LayoutBuilder::addItem() or
  175. \c LayoutBuilder::addItems() and never stored in user code.
  176. */
  177. /*!
  178. Constructs a layout item instance representing an empty cell.
  179. */
  180. LayoutItem::LayoutItem() = default;
  181. LayoutItem::~LayoutItem() = default;
  182. /*!
  183. \fn template <class T> LayoutItem(const T &t)
  184. \internal
  185. Constructs a layout item proxy for \a t.
  186. T could be
  187. \list
  188. \li \c {QString}
  189. \li \c {QWidget *}
  190. \li \c {QLayout *}
  191. \endlist
  192. */
  193. struct ResultItem
  194. {
  195. ResultItem() = default;
  196. explicit ResultItem(QLayout *l)
  197. : layout(l)
  198. , empty(!l)
  199. {}
  200. explicit ResultItem(QWidget *w)
  201. : widget(w)
  202. , empty(!w)
  203. {}
  204. QString text;
  205. QLayout *layout = nullptr;
  206. QWidget *widget = nullptr;
  207. int space = -1;
  208. int stretch = -1;
  209. int span = 1;
  210. bool empty = false;
  211. };
  212. struct Slice
  213. {
  214. Slice() = default;
  215. Slice(QLayout *l)
  216. : layout(l)
  217. {}
  218. Slice(QWidget *w, bool isLayouting = false)
  219. : widget(w)
  220. , isLayouting(isLayouting)
  221. {}
  222. QLayout *layout = nullptr;
  223. QWidget *widget = nullptr;
  224. void flush();
  225. // Grid-specific
  226. int currentGridColumn = 0;
  227. int currentGridRow = 0;
  228. bool isFormAlignment = false;
  229. bool isLayouting = false;
  230. Qt::Alignment align = {}; // Can be changed to
  231. // Grid or Form
  232. QList<ResultItem> pendingItems;
  233. };
  234. static QWidget *widgetForItem(QLayoutItem *item)
  235. {
  236. if (QWidget *w = item->widget())
  237. return w;
  238. if (item->spacerItem())
  239. return nullptr;
  240. QLayout *l = item->layout();
  241. if (!l)
  242. return nullptr;
  243. for (int i = 0, n = l->count(); i < n; ++i) {
  244. if (QWidget *w = widgetForItem(l->itemAt(i)))
  245. return w;
  246. }
  247. return nullptr;
  248. }
  249. static QLabel *createLabel(const QString &text)
  250. {
  251. auto label = new QLabel(text);
  252. label->setTextInteractionFlags(Qt::TextSelectableByMouse);
  253. return label;
  254. }
  255. static void addItemToBoxLayout(QBoxLayout *layout, const ResultItem &item)
  256. {
  257. if (QWidget *w = item.widget) {
  258. layout->addWidget(w);
  259. } else if (QLayout *l = item.layout) {
  260. layout->addLayout(l);
  261. } else if (item.stretch != -1) {
  262. layout->addStretch(item.stretch);
  263. } else if (item.space != -1) {
  264. layout->addSpacing(item.space);
  265. } else if (!item.text.isEmpty()) {
  266. layout->addWidget(createLabel(item.text));
  267. } else if (item.empty) {
  268. // Nothing to do, but no reason to warn, either.
  269. } else {
  270. QTC_CHECK(false);
  271. }
  272. }
  273. static void addItemToFlowLayout(FlowLayout *layout, const ResultItem &item)
  274. {
  275. if (QWidget *w = item.widget) {
  276. layout->addWidget(w);
  277. } else if (QLayout *l = item.layout) {
  278. layout->addItem(l);
  279. // } else if (item.stretch != -1) {
  280. // layout->addStretch(item.stretch);
  281. // } else if (item.space != -1) {
  282. // layout->addSpacing(item.space);
  283. } else if (item.empty) {
  284. // Nothing to do, but no reason to warn, either
  285. } else if (!item.text.isEmpty()) {
  286. layout->addWidget(createLabel(item.text));
  287. } else {
  288. QTC_CHECK(false);
  289. }
  290. }
  291. void Slice::flush()
  292. {
  293. if (pendingItems.empty())
  294. return;
  295. if (auto formLayout = qobject_cast<QFormLayout *>(layout)) {
  296. // If there are more than two items, we cram the last ones in one hbox.
  297. if (pendingItems.size() > 2) {
  298. auto hbox = new QHBoxLayout;
  299. hbox->setContentsMargins(0, 0, 0, 0);
  300. for (int i = 1; i < pendingItems.size(); ++i)
  301. addItemToBoxLayout(hbox, pendingItems.at(i));
  302. while (pendingItems.size() > 1)
  303. pendingItems.pop_back();
  304. pendingItems.append(ResultItem(hbox));
  305. }
  306. if (pendingItems.size() == 1) { // One one item given, so this spans both columns.
  307. const ResultItem &f0 = pendingItems.at(0);
  308. if (auto layout = f0.layout)
  309. formLayout->addRow(layout);
  310. else if (auto widget = f0.widget)
  311. formLayout->addRow(widget);
  312. } else if (pendingItems.size() == 2) { // Normal case, both columns used.
  313. ResultItem &f1 = pendingItems[1];
  314. const ResultItem &f0 = pendingItems.at(0);
  315. if (!f1.widget && !f1.layout && !f1.text.isEmpty())
  316. f1.widget = createLabel(f1.text);
  317. if (f0.widget) {
  318. if (f1.layout)
  319. formLayout->addRow(f0.widget, f1.layout);
  320. else if (f1.widget)
  321. formLayout->addRow(f0.widget, f1.widget);
  322. } else {
  323. if (f1.layout)
  324. formLayout->addRow(createLabel(f0.text), f1.layout);
  325. else if (f1.widget)
  326. formLayout->addRow(createLabel(f0.text), f1.widget);
  327. }
  328. } else {
  329. QTC_CHECK(false);
  330. }
  331. // Set up label as buddy if possible.
  332. const int lastRow = formLayout->rowCount() - 1;
  333. QLayoutItem *l = formLayout->itemAt(lastRow, QFormLayout::LabelRole);
  334. QLayoutItem *f = formLayout->itemAt(lastRow, QFormLayout::FieldRole);
  335. if (l && f) {
  336. if (QLabel *label = qobject_cast<QLabel *>(l->widget())) {
  337. if (QWidget *widget = widgetForItem(f))
  338. label->setBuddy(widget);
  339. }
  340. }
  341. } else if (auto gridLayout = qobject_cast<QGridLayout *>(layout)) {
  342. for (const ResultItem &item : std::as_const(pendingItems)) {
  343. Qt::Alignment a = currentGridColumn == 0 ? align : Qt::Alignment();
  344. if (item.widget)
  345. gridLayout
  346. ->addWidget(item.widget, currentGridRow, currentGridColumn, 1, item.span, a);
  347. else if (item.layout)
  348. gridLayout
  349. ->addLayout(item.layout, currentGridRow, currentGridColumn, 1, item.span, a);
  350. else if (!item.text.isEmpty())
  351. gridLayout
  352. ->addWidget(createLabel(item.text), currentGridRow, currentGridColumn, 1, 1, a);
  353. currentGridColumn += item.span;
  354. }
  355. ++currentGridRow;
  356. currentGridColumn = 0;
  357. } else if (auto boxLayout = qobject_cast<QBoxLayout *>(layout)) {
  358. for (const ResultItem &item : std::as_const(pendingItems))
  359. addItemToBoxLayout(boxLayout, item);
  360. } else if (auto flowLayout = qobject_cast<FlowLayout *>(layout)) {
  361. for (const ResultItem &item : std::as_const(pendingItems))
  362. addItemToFlowLayout(flowLayout, item);
  363. } else {
  364. QTC_CHECK(false);
  365. }
  366. pendingItems.clear();
  367. }
  368. // LayoutBuilder
  369. class LayoutBuilder
  370. {
  371. Q_DISABLE_COPY_MOVE(LayoutBuilder)
  372. public:
  373. LayoutBuilder();
  374. ~LayoutBuilder();
  375. void addItem(const LayoutItem &item);
  376. void addItems(const LayoutItems &items);
  377. QList<Slice> stack;
  378. };
  379. static void addItemHelper(LayoutBuilder &builder, const LayoutItem &item)
  380. {
  381. if (item.onAdd)
  382. item.onAdd(builder);
  383. if (item.setter) {
  384. if (QWidget *widget = builder.stack.last().widget)
  385. item.setter(widget);
  386. else if (QLayout *layout = builder.stack.last().layout)
  387. item.setter(layout);
  388. else
  389. QTC_CHECK(false);
  390. }
  391. for (const LayoutItem &subItem : item.subItems)
  392. addItemHelper(builder, subItem);
  393. if (item.onExit)
  394. item.onExit(builder);
  395. }
  396. void doAddText(LayoutBuilder &builder, const QString &text)
  397. {
  398. ResultItem fi;
  399. fi.text = text;
  400. builder.stack.last().pendingItems.append(fi);
  401. }
  402. void doAddSpace(LayoutBuilder &builder, const Space &space)
  403. {
  404. ResultItem fi;
  405. fi.space = space.space;
  406. builder.stack.last().pendingItems.append(fi);
  407. }
  408. void doAddStretch(LayoutBuilder &builder, const Stretch &stretch)
  409. {
  410. ResultItem fi;
  411. fi.stretch = stretch.stretch;
  412. builder.stack.last().pendingItems.append(fi);
  413. }
  414. void doAddLayout(LayoutBuilder &builder, QLayout *layout)
  415. {
  416. builder.stack.last().pendingItems.append(ResultItem(layout));
  417. }
  418. void doAddWidget(LayoutBuilder &builder, QWidget *widget)
  419. {
  420. builder.stack.last().pendingItems.append(ResultItem(widget));
  421. }
  422. /*!
  423. \class Layouting::Space
  424. \inmodule QtCreator
  425. \brief The Space class represents some empty space in a layout.
  426. */
  427. /*!
  428. \class Layouting::Stretch
  429. \inmodule QtCreator
  430. \brief The Stretch class represents some stretch in a layout.
  431. */
  432. /*!
  433. \class Layouting::LayoutBuilder
  434. \internal
  435. \inmodule QtCreator
  436. \brief The LayoutBuilder class provides a convenient way to fill \c QFormLayout
  437. and \c QGridLayouts with contents.
  438. Filling a layout with items happens item-by-item, row-by-row.
  439. A LayoutBuilder instance is typically used locally within a function and never stored.
  440. \sa addItem(), addItems()
  441. */
  442. LayoutBuilder::LayoutBuilder() = default;
  443. /*!
  444. \internal
  445. Destructs a layout builder.
  446. */
  447. LayoutBuilder::~LayoutBuilder() = default;
  448. void LayoutBuilder::addItem(const LayoutItem &item)
  449. {
  450. addItemHelper(*this, item);
  451. }
  452. void LayoutBuilder::addItems(const LayoutItems &items)
  453. {
  454. for (const LayoutItem &item : items)
  455. addItemHelper(*this, item);
  456. }
  457. /*!
  458. Starts a new row containing \a items. The row can be further extended by
  459. other items using \c addItem() or \c addItems().
  460. \sa addItem(), addItems()
  461. */
  462. void LayoutItem::addRow(const LayoutItems &items)
  463. {
  464. addItem(br);
  465. addItems(items);
  466. }
  467. /*!
  468. Adds the layout item \a item as sub items.
  469. */
  470. void LayoutItem::addItem(const LayoutItem &item)
  471. {
  472. subItems.append(item);
  473. }
  474. /*!
  475. Adds the layout items \a items as sub items.
  476. */
  477. void LayoutItem::addItems(const LayoutItems &items)
  478. {
  479. subItems.append(items);
  480. }
  481. /*!
  482. Attaches the constructed layout to the provided QWidget \a w.
  483. This operation can only be performed once per LayoutBuilder instance.
  484. */
  485. void LayoutItem::attachTo(QWidget *w) const
  486. {
  487. LayoutBuilder builder;
  488. builder.stack.append(w);
  489. addItemHelper(builder, *this);
  490. }
  491. QWidget *LayoutItem::emerge()
  492. {
  493. LayoutBuilder builder;
  494. builder.stack.append(Slice());
  495. addItemHelper(builder, *this);
  496. if (builder.stack.empty())
  497. return nullptr;
  498. QTC_ASSERT(builder.stack.last().pendingItems.size() == 1, return nullptr);
  499. ResultItem ri = builder.stack.last().pendingItems.takeFirst();
  500. QTC_ASSERT(ri.layout || ri.widget, return nullptr);
  501. if (ri.layout) {
  502. auto w = new QWidget;
  503. w->setLayout(ri.layout);
  504. return w;
  505. }
  506. return ri.widget;
  507. }
  508. static void layoutExit(LayoutBuilder &builder)
  509. {
  510. builder.stack.last().flush();
  511. QLayout *layout = builder.stack.last().layout;
  512. builder.stack.pop_back();
  513. if (builder.stack.last().isLayouting) {
  514. builder.stack.last().pendingItems.append(ResultItem(layout));
  515. } else if (QWidget *widget = builder.stack.last().widget) {
  516. widget->setLayout(layout);
  517. } else
  518. builder.stack.last().pendingItems.append(ResultItem(layout));
  519. }
  520. template<class T>
  521. static void layoutingWidgetExit(LayoutBuilder &builder)
  522. {
  523. const Slice slice = builder.stack.last();
  524. T *w = qobject_cast<T *>(slice.widget);
  525. for (const ResultItem &ri : slice.pendingItems) {
  526. if (ri.widget) {
  527. w->addWidget(ri.widget);
  528. } else if (ri.layout) {
  529. auto child = new QWidget;
  530. child->setLayout(ri.layout);
  531. w->addWidget(child);
  532. }
  533. }
  534. builder.stack.pop_back();
  535. builder.stack.last().pendingItems.append(ResultItem(w));
  536. }
  537. static void widgetExit(LayoutBuilder &builder)
  538. {
  539. QWidget *widget = builder.stack.last().widget;
  540. builder.stack.pop_back();
  541. builder.stack.last().pendingItems.append(ResultItem(widget));
  542. }
  543. Column::Column(std::initializer_list<LayoutItem> items)
  544. {
  545. subItems = items;
  546. onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QVBoxLayout); };
  547. onExit = layoutExit;
  548. }
  549. Row::Row(std::initializer_list<LayoutItem> items)
  550. {
  551. subItems = items;
  552. onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QHBoxLayout); };
  553. onExit = layoutExit;
  554. }
  555. Flow::Flow(std::initializer_list<LayoutItem> items)
  556. {
  557. subItems = items;
  558. onAdd = [](LayoutBuilder &builder) { builder.stack.append(new FlowLayout); };
  559. onExit = layoutExit;
  560. }
  561. Grid::Grid(std::initializer_list<LayoutItem> items)
  562. {
  563. subItems = items;
  564. onAdd = [](LayoutBuilder &builder) { builder.stack.append(new QGridLayout); };
  565. onExit = layoutExit;
  566. }
  567. static QFormLayout *newFormLayout()
  568. {
  569. auto formLayout = new QFormLayout;
  570. formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
  571. return formLayout;
  572. }
  573. Form::Form(std::initializer_list<LayoutItem> items)
  574. {
  575. subItems = items;
  576. onAdd = [](LayoutBuilder &builder) { builder.stack.append(newFormLayout()); };
  577. onExit = layoutExit;
  578. }
  579. LayoutItem br()
  580. {
  581. LayoutItem item;
  582. item.onAdd = [](LayoutBuilder &builder) { builder.stack.last().flush(); };
  583. return item;
  584. }
  585. LayoutItem empty()
  586. {
  587. LayoutItem item;
  588. item.onAdd = [](LayoutBuilder &builder) {
  589. ResultItem ri;
  590. ri.empty = true;
  591. builder.stack.last().pendingItems.append(ri);
  592. };
  593. return item;
  594. }
  595. LayoutItem hr()
  596. {
  597. LayoutItem item;
  598. item.onAdd = [](LayoutBuilder &builder) { doAddWidget(builder, createHr()); };
  599. return item;
  600. }
  601. LayoutItem st()
  602. {
  603. LayoutItem item;
  604. item.onAdd = [](LayoutBuilder &builder) { doAddStretch(builder, Stretch(1)); };
  605. return item;
  606. }
  607. LayoutItem noMargin()
  608. {
  609. return customMargin({});
  610. }
  611. LayoutItem normalMargin()
  612. {
  613. return customMargin({9, 9, 9, 9});
  614. }
  615. LayoutItem customMargin(const QMargins &margin)
  616. {
  617. LayoutItem item;
  618. item.onAdd = [margin](LayoutBuilder &builder) {
  619. if (auto layout = builder.stack.last().layout)
  620. layout->setContentsMargins(margin);
  621. else if (auto widget = builder.stack.last().widget)
  622. widget->setContentsMargins(margin);
  623. };
  624. return item;
  625. }
  626. LayoutItem withFormAlignment()
  627. {
  628. LayoutItem item;
  629. item.onAdd = [](LayoutBuilder &builder) {
  630. if (builder.stack.size() >= 2) {
  631. if (auto widget = builder.stack.at(builder.stack.size() - 2).widget) {
  632. const Qt::Alignment align(
  633. widget->style()->styleHint(QStyle::SH_FormLayoutLabelAlignment));
  634. builder.stack.last().align = align;
  635. }
  636. }
  637. };
  638. return item;
  639. }
  640. // "Widgets"
  641. template<class T>
  642. void setupWidget(LayoutItem *item)
  643. {
  644. item->onAdd = [](LayoutBuilder &builder) { builder.stack.append(new T); };
  645. item->onExit = widgetExit;
  646. };
  647. Widget::Widget(std::initializer_list<LayoutItem> items)
  648. {
  649. this->subItems = items;
  650. setupWidget<QWidget>(this);
  651. }
  652. Group::Group(std::initializer_list<LayoutItem> items)
  653. {
  654. this->subItems = items;
  655. setupWidget<QGroupBox>(this);
  656. }
  657. Stack::Stack(std::initializer_list<LayoutItem> items)
  658. {
  659. // We use a QStackedWidget instead of a QStackedLayout here because the latter will call
  660. // "setVisible()" when a child is added, which can lead to the widget being spawned as a
  661. // top-level widget. This can lead to the focus shifting away from the main application.
  662. subItems = items;
  663. onAdd = [](LayoutBuilder &builder) { builder.stack.append(Slice(new QStackedWidget, true)); };
  664. onExit = layoutingWidgetExit<QStackedWidget>;
  665. }
  666. PushButton::PushButton(std::initializer_list<LayoutItem> items)
  667. {
  668. this->subItems = items;
  669. setupWidget<QPushButton>(this);
  670. }
  671. SpinBox::SpinBox(std::initializer_list<LayoutItem> items)
  672. {
  673. this->subItems = items;
  674. setupWidget<QSpinBox>(this);
  675. }
  676. TextEdit::TextEdit(std::initializer_list<LayoutItem> items)
  677. {
  678. this->subItems = items;
  679. setupWidget<QTextEdit>(this);
  680. }
  681. Splitter::Splitter(std::initializer_list<LayoutItem> items)
  682. {
  683. subItems = items;
  684. onAdd = [](LayoutBuilder &builder) {
  685. auto splitter = new QSplitter;
  686. splitter->setOrientation(Qt::Vertical);
  687. builder.stack.append(Slice(splitter, true));
  688. };
  689. onExit = layoutingWidgetExit<QSplitter>;
  690. }
  691. ToolBar::ToolBar(std::initializer_list<LayoutItem> items)
  692. {
  693. subItems = items;
  694. onAdd = [](LayoutBuilder &builder) {
  695. auto toolbar = new QToolBar;
  696. toolbar->setOrientation(Qt::Horizontal);
  697. builder.stack.append(Slice(toolbar, true));
  698. };
  699. onExit = layoutingWidgetExit<QToolBar>;
  700. }
  701. TabWidget::TabWidget(std::initializer_list<LayoutItem> items)
  702. {
  703. this->subItems = items;
  704. setupWidget<QTabWidget>(this);
  705. }
  706. // Special Tab
  707. Tab::Tab(const QString &tabName, const LayoutItem &item)
  708. {
  709. onAdd = [item](LayoutBuilder &builder) {
  710. auto tab = new QWidget;
  711. builder.stack.append(tab);
  712. item.attachTo(tab);
  713. };
  714. onExit = [tabName](LayoutBuilder &builder) {
  715. QWidget *inner = builder.stack.last().widget;
  716. builder.stack.pop_back();
  717. auto tabWidget = qobject_cast<QTabWidget *>(builder.stack.last().widget);
  718. QTC_ASSERT(tabWidget, return);
  719. tabWidget->addTab(inner, tabName);
  720. };
  721. }
  722. // Special If
  723. If::If(bool condition, const LayoutItems &items, const LayoutItems &other)
  724. {
  725. subItems.append(condition ? items : other);
  726. }
  727. // Special Application
  728. Application::Application(std::initializer_list<LayoutItem> items)
  729. {
  730. subItems = items;
  731. setupWidget<QWidget>(this);
  732. onExit = {}; // Hack: Don't dropp the last slice, we need the resulting widget.
  733. }
  734. int Application::exec(int &argc, char *argv[])
  735. {
  736. QApplication app(argc, argv);
  737. LayoutBuilder builder;
  738. addItemHelper(builder, *this);
  739. if (QWidget *widget = builder.stack.last().widget)
  740. widget->show();
  741. return app.exec();
  742. }
  743. // "Properties"
  744. LayoutItem title(const QString &title)
  745. {
  746. return [title](QObject *target) {
  747. if (auto groupBox = qobject_cast<QGroupBox *>(target)) {
  748. groupBox->setTitle(title);
  749. groupBox->setObjectName(title);
  750. } else if (auto widget = qobject_cast<QWidget *>(target)) {
  751. widget->setWindowTitle(title);
  752. } else {
  753. QTC_CHECK(false);
  754. }
  755. };
  756. }
  757. LayoutItem windowTitle(const QString &windowTitle)
  758. {
  759. return [windowTitle](QObject *target) {
  760. if (auto widget = qobject_cast<QWidget *>(target)) {
  761. widget->setWindowTitle(windowTitle);
  762. } else {
  763. QTC_CHECK(false);
  764. }
  765. };
  766. }
  767. LayoutItem text(const QString &text)
  768. {
  769. return [text](QObject *target) {
  770. if (auto button = qobject_cast<QAbstractButton *>(target)) {
  771. button->setText(text);
  772. } else if (auto textEdit = qobject_cast<QTextEdit *>(target)) {
  773. textEdit->setText(text);
  774. } else {
  775. QTC_CHECK(false);
  776. }
  777. };
  778. }
  779. LayoutItem tooltip(const QString &toolTip)
  780. {
  781. return [toolTip](QObject *target) {
  782. if (auto widget = qobject_cast<QWidget *>(target)) {
  783. widget->setToolTip(toolTip);
  784. } else {
  785. QTC_CHECK(false);
  786. }
  787. };
  788. }
  789. LayoutItem spacing(int spacing)
  790. {
  791. return [spacing](QObject *target) {
  792. if (auto layout = qobject_cast<QLayout *>(target)) {
  793. layout->setSpacing(spacing);
  794. } else {
  795. QTC_CHECK(false);
  796. }
  797. };
  798. }
  799. LayoutItem resize(int w, int h)
  800. {
  801. return [w, h](QObject *target) {
  802. if (auto widget = qobject_cast<QWidget *>(target)) {
  803. widget->resize(w, h);
  804. } else {
  805. QTC_CHECK(false);
  806. }
  807. };
  808. }
  809. LayoutItem columnStretch(int column, int stretch)
  810. {
  811. return [column, stretch](QObject *target) {
  812. if (auto grid = qobject_cast<QGridLayout *>(target)) {
  813. grid->setColumnStretch(column, stretch);
  814. } else {
  815. QTC_CHECK(false);
  816. }
  817. };
  818. }
  819. LayoutItem fieldGrowthPolicy(QFormLayout::FieldGrowthPolicy policy)
  820. {
  821. return [policy](QObject *target) {
  822. if (auto form = qobject_cast<QFormLayout *>(target)) {
  823. form->setFieldGrowthPolicy(policy);
  824. } else {
  825. QTC_CHECK(false);
  826. }
  827. };
  828. }
  829. // Id based setters
  830. LayoutItem id(ID &out)
  831. {
  832. return [&out](QObject *target) { out.ob = target; };
  833. }
  834. void setText(ID id, const QString &text)
  835. {
  836. if (auto textEdit = qobject_cast<QTextEdit *>(id.ob))
  837. textEdit->setText(text);
  838. }
  839. // Signals
  840. LayoutItem onClicked(const std::function<void()> &func, QObject *guard)
  841. {
  842. return [func, guard](QObject *target) {
  843. if (auto button = qobject_cast<QAbstractButton *>(target)) {
  844. QObject::connect(button, &QAbstractButton::clicked, guard ? guard : target, func);
  845. } else {
  846. QTC_CHECK(false);
  847. }
  848. };
  849. }
  850. LayoutItem onTextChanged(const std::function<void(const QString &)> &func, QObject *guard)
  851. {
  852. return [func, guard](QObject *target) {
  853. if (auto button = qobject_cast<QSpinBox *>(target)) {
  854. QObject::connect(button, &QSpinBox::textChanged, guard ? guard : target, func);
  855. } else {
  856. QTC_CHECK(false);
  857. }
  858. };
  859. }
  860. LayoutItem onValueChanged(const std::function<void(int)> &func, QObject *guard)
  861. {
  862. return [func, guard](QObject *target) {
  863. if (auto button = qobject_cast<QSpinBox *>(target)) {
  864. QObject::connect(button,
  865. QOverload<int>::of(&QSpinBox::valueChanged),
  866. guard ? guard : target,
  867. func);
  868. } else {
  869. QTC_CHECK(false);
  870. }
  871. };
  872. }
  873. // Convenience
  874. QWidget *createHr(QWidget *parent)
  875. {
  876. auto frame = new QFrame(parent);
  877. frame->setFrameShape(QFrame::HLine);
  878. frame->setFrameShadow(QFrame::Sunken);
  879. return frame;
  880. }
  881. // Singletons.
  882. LayoutItem::LayoutItem(const LayoutItem &t)
  883. {
  884. operator=(t);
  885. }
  886. void createItem(LayoutItem *item, LayoutItem (*t)())
  887. {
  888. *item = t();
  889. }
  890. void createItem(LayoutItem *item, const std::function<void(QObject *target)> &t)
  891. {
  892. item->setter = t;
  893. }
  894. void createItem(LayoutItem *item, QWidget *t)
  895. {
  896. if (auto l = qobject_cast<QLabel *>(t))
  897. l->setTextInteractionFlags(l->textInteractionFlags() | Qt::TextSelectableByMouse);
  898. item->onAdd = [t](LayoutBuilder &builder) { doAddWidget(builder, t); };
  899. }
  900. void createItem(LayoutItem *item, QLayout *t)
  901. {
  902. item->onAdd = [t](LayoutBuilder &builder) { doAddLayout(builder, t); };
  903. }
  904. void createItem(LayoutItem *item, const QString &t)
  905. {
  906. item->onAdd = [t](LayoutBuilder &builder) { doAddText(builder, t); };
  907. }
  908. void createItem(LayoutItem *item, const Space &t)
  909. {
  910. item->onAdd = [t](LayoutBuilder &builder) { doAddSpace(builder, t); };
  911. }
  912. void createItem(LayoutItem *item, const Stretch &t)
  913. {
  914. item->onAdd = [t](LayoutBuilder &builder) { doAddStretch(builder, t); };
  915. }
  916. void createItem(LayoutItem *item, const Span &t)
  917. {
  918. item->onAdd = [t](LayoutBuilder &builder) {
  919. addItemHelper(builder, t.item);
  920. builder.stack.last().pendingItems.last().span = t.span;
  921. };
  922. }
  923. } // namespace Layouting
  924. #include "layoutbuilder.moc"