1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "layout_p.h"
5#include "layoutdecoration.h"
6#include "qdesigner_utils_p.h"
7#include "qdesigner_widgetitem_p.h"
8#include "qlayout_widget_p.h"
9#include "spacer_widget_p.h"
10#include "widgetfactory_p.h"
11
12#include <QtDesigner/abstractformeditor.h>
13#include <QtDesigner/abstractformwindow.h>
14#include <QtDesigner/abstractmetadatabase.h>
15#include <QtDesigner/abstractwidgetdatabase.h>
16#include <QtDesigner/container.h>
17#include <QtDesigner/propertysheet.h>
18#include <QtDesigner/qextensionmanager.h>
19
20#include <QtCore/qdebug.h>
21#include <QtCore/qhash.h>
22#include <QtCore/qlist.h>
23#include <QtCore/qset.h>
24
25#include <QtGui/qbitmap.h>
26#include <QtGui/qevent.h>
27#include <QtGui/qpainter.h>
28
29#include <QtWidgets/qapplication.h>
30#include <QtWidgets/qformlayout.h>
31#include <QtWidgets/qgridlayout.h>
32#include <QtWidgets/qlabel.h>
33#include <QtWidgets/qmainwindow.h>
34#include <QtWidgets/qscrollarea.h>
35#include <QtWidgets/qsplitter.h>
36#include <QtWidgets/qwizard.h>
37
38#include <algorithm>
39
40QT_BEGIN_NAMESPACE
41
42using namespace Qt::StringLiterals;
43
44namespace qdesigner_internal {
45
46/* The wizard has a policy of setting a size policy of its external children
47 * according to the page being expanding or not (in the latter case, the
48 * page will be pushed to the top). When setting/breaking layouts, this needs
49 * to be updated, which happens via a fake style change event. */
50
51void updateWizardLayout(QWidget *layoutBase);
52
53class FriendlyWizardPage : public QWizardPage {
54 friend void updateWizardLayout(QWidget *);
55};
56
57void updateWizardLayout(QWidget *layoutBase)
58{
59 if (QWizardPage *wizardPage = qobject_cast<QWizardPage*>(object: layoutBase))
60 if (QWizard *wizard = static_cast<FriendlyWizardPage*>(wizardPage)->wizard()) {
61 QEvent event(QEvent::StyleChange);
62 QApplication::sendEvent(receiver: wizard, event: &event);
63 }
64}
65
66/*!
67 \class qdesigner_internal::Layout
68 \brief Baseclass for layouting widgets in the Designer (Helper for Layout commands)
69 \internal
70
71 Classes derived from this abstract base class are used for layouting
72 operations in the Designer (creating/breaking layouts).
73
74 Instances live in the Layout/BreakLayout commands.
75*/
76
77/*! \a p specifies the parent of the layoutBase \a lb. The parent
78 might be changed in setup(). If the layoutBase is a
79 container, the parent and the layoutBase are the same. Also they
80 always have to be a widget known to the designer (e.g. in the case
81 of the tabwidget parent and layoutBase are the tabwidget and not the
82 page which actually gets laid out. For actual usage the correct
83 widget is found later by Layout.)
84 */
85
86Layout::Layout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, LayoutInfo::Type layoutType) :
87 m_widgets(wl),
88 m_parentWidget(p),
89 m_layoutBase(lb),
90 m_formWindow(fw),
91 m_layoutType(layoutType),
92 m_reparentLayoutWidget(true),
93 m_isBreak(false)
94{
95 if (m_layoutBase)
96 m_oldGeometry = m_layoutBase->geometry();
97}
98
99Layout::~Layout() = default;
100
101/*! The widget list we got in the constructor might contain too much
102 widgets (like widgets with different parents, already laid out
103 widgets, etc.). Here we set up the list and so the only the "best"
104 widgets get laid out.
105*/
106
107void Layout::setup()
108{
109 m_startPoint = QPoint(32767, 32767);
110
111 // Go through all widgets of the list we got. As we can only
112 // layout widgets which have the same parent, we first do some
113 // sorting which means create a list for each parent containing
114 // its child here. After that we keep working on the list of
115 // children which has the most entries.
116 // Widgets which are already laid out are thrown away here too
117
118 QMultiMap<QWidget*, QWidget*> lists;
119 for (QWidget *w : std::as_const(t&: m_widgets)) {
120 QWidget *p = w->parentWidget();
121
122 if (p && LayoutInfo::layoutType(core: m_formWindow->core(), w: p) != LayoutInfo::NoLayout
123 && m_formWindow->core()->metaDataBase()->item(object: p->layout()) != nullptr)
124 continue;
125
126 lists.insert(key: p, value: w);
127 }
128
129 QWidgetList lastList;
130 const QWidgetList &parents = lists.keys();
131 for (QWidget *p : parents) {
132 if (lists.count(key: p) > lastList.size())
133 lastList = lists.values(key: p);
134 }
135
136
137 // If we found no list (because no widget did fit at all) or the
138 // best list has only one entry and we do not layout a container,
139 // we leave here.
140 QDesignerWidgetDataBaseInterface *widgetDataBase = m_formWindow->core()->widgetDataBase();
141 if (lastList.size() < 2 &&
142 (!m_layoutBase ||
143 (!widgetDataBase->isContainer(object: m_layoutBase, resolveName: false) &&
144 m_layoutBase != m_formWindow->mainContainer()))
145 ) {
146 m_widgets.clear();
147 m_startPoint = QPoint(0, 0);
148 return;
149 }
150
151 // Now we have a new and clean widget list, which makes sense
152 // to layout
153 m_widgets = lastList;
154 // Also use the only correct parent later, so store it
155
156 Q_ASSERT(m_widgets.isEmpty() == false);
157
158 m_parentWidget = m_formWindow->core()->widgetFactory()->widgetOfContainer(w: m_widgets.first()->parentWidget());
159 // Now calculate the position where the layout-meta-widget should
160 // be placed and connect to widgetDestroyed() signals of the
161 // widgets to get informed if one gets deleted to be able to
162 // handle that and do not crash in this case
163 for (QWidget *w : std::as_const(t&: m_widgets)) {
164 connect(sender: w, signal: &QObject::destroyed, context: this, slot: &Layout::widgetDestroyed);
165 m_startPoint = QPoint(qMin(a: m_startPoint.x(), b: w->x()), qMin(a: m_startPoint.y(), b: w->y()));
166 const QRect rc(w->geometry());
167
168 m_geometries.insert(key: w, value: rc);
169 // Change the Z-order, as saving/loading uses the Z-order for
170 // writing/creating widgets and this has to be the same as in
171 // the layout. Else saving + loading will give different results
172 w->raise();
173 }
174
175 sort();
176}
177
178void Layout::widgetDestroyed()
179{
180 if (QWidget *w = qobject_cast<QWidget *>(o: sender())) {
181 m_widgets.removeAt(i: m_widgets.indexOf(t: w));
182 m_geometries.remove(key: w);
183 }
184}
185
186bool Layout::prepareLayout(bool &needMove, bool &needReparent)
187{
188 for (QWidget *widget : std::as_const(t&: m_widgets))
189 widget->raise();
190
191 needMove = !m_layoutBase;
192 needReparent = needMove || (m_reparentLayoutWidget && qobject_cast<QLayoutWidget*>(object: m_layoutBase)) || qobject_cast<QSplitter*>(object: m_layoutBase);
193
194 QDesignerWidgetFactoryInterface *widgetFactory = m_formWindow->core()->widgetFactory();
195 QDesignerMetaDataBaseInterface *metaDataBase = m_formWindow->core()->metaDataBase();
196
197 if (m_layoutBase == nullptr) {
198 const bool useSplitter = m_layoutType == LayoutInfo::HSplitter || m_layoutType == LayoutInfo::VSplitter;
199 const QString baseWidgetClassName = useSplitter ? u"QSplitter"_s : u"QLayoutWidget"_s;
200 m_layoutBase = widgetFactory->createWidget(name: baseWidgetClassName, parentWidget: widgetFactory->containerOfWidget(w: m_parentWidget));
201 if (useSplitter) {
202 m_layoutBase->setObjectName(u"splitter"_s);
203 m_formWindow->ensureUniqueObjectName(object: m_layoutBase);
204 }
205 } else {
206 LayoutInfo::deleteLayout(core: m_formWindow->core(), widget: m_layoutBase);
207 }
208
209 metaDataBase->add(object: m_layoutBase);
210
211 Q_ASSERT(m_layoutBase->layout() == nullptr || metaDataBase->item(m_layoutBase->layout()) == nullptr);
212
213 return true;
214}
215
216static bool isMainContainer(QDesignerFormWindowInterface *fw, const QWidget *w)
217{
218 return w && (w == fw || w == fw->mainContainer());
219}
220
221static bool isPageOfContainerWidget(QDesignerFormWindowInterface *fw, QWidget *widget)
222{
223 QDesignerContainerExtension *c = qt_extension<QDesignerContainerExtension*>(
224 manager: fw->core()->extensionManager(), object: widget->parentWidget());
225
226 if (c != nullptr) {
227 for (int i = 0; i<c->count(); ++i) {
228 if (widget == c->widget(index: i))
229 return true;
230 }
231 }
232
233 return false;
234}
235void Layout::finishLayout(bool needMove, QLayout *layout)
236{
237 if (m_parentWidget == m_layoutBase) {
238 QWidget *widget = m_layoutBase;
239 m_oldGeometry = widget->geometry();
240
241 bool done = false;
242 while (!isMainContainer(fw: m_formWindow, w: widget) && !done) {
243 if (!m_formWindow->isManaged(widget)) {
244 widget = widget->parentWidget();
245 continue;
246 }
247 if (LayoutInfo::isWidgetLaidout(core: m_formWindow->core(), widget)) {
248 widget = widget->parentWidget();
249 continue;
250 }
251 if (isPageOfContainerWidget(fw: m_formWindow, widget)) {
252 widget = widget->parentWidget();
253 continue;
254 }
255 if (widget->parentWidget()) {
256 QScrollArea *area = qobject_cast<QScrollArea*>(object: widget->parentWidget()->parentWidget());
257 if (area && area->widget() == widget) {
258 widget = area;
259 continue;
260 }
261 }
262
263 done = true;
264 }
265 updateWizardLayout(layoutBase: m_layoutBase);
266 QApplication::processEvents(flags: QEventLoop::ExcludeUserInputEvents);
267 // We don't want to resize the form window
268 if (!Utils::isCentralWidget(fw: m_formWindow, widget))
269 widget->adjustSize();
270
271 return;
272 }
273
274 if (needMove)
275 m_layoutBase->move(m_startPoint);
276
277 const QRect g(m_layoutBase->pos(), m_layoutBase->size());
278
279 if (LayoutInfo::layoutType(core: m_formWindow->core(), w: m_layoutBase->parentWidget()) == LayoutInfo::NoLayout && !m_isBreak)
280 m_layoutBase->adjustSize();
281 else if (m_isBreak)
282 m_layoutBase->setGeometry(m_oldGeometry);
283
284 m_oldGeometry = g;
285 if (layout)
286 layout->invalidate();
287 m_layoutBase->show();
288
289 if (qobject_cast<QLayoutWidget*>(object: m_layoutBase) || qobject_cast<QSplitter*>(object: m_layoutBase)) {
290 m_formWindow->clearSelection(changePropertyDisplay: false);
291 m_formWindow->manageWidget(widget: m_layoutBase);
292 m_formWindow->selectWidget(w: m_layoutBase);
293 }
294}
295
296void Layout::undoLayout()
297{
298 if (m_widgets.isEmpty())
299 return;
300
301 m_formWindow->selectWidget(w: m_layoutBase, select: false);
302
303 QDesignerWidgetFactoryInterface *widgetFactory = m_formWindow->core()->widgetFactory();
304 for (auto it = m_geometries.cbegin(), end = m_geometries.cend(); it != end; ++it) {
305 if (!it.key())
306 continue;
307
308 QWidget* w = it.key();
309 const QRect rc = it.value();
310
311 const bool showIt = w->isVisibleTo(m_formWindow);
312 QWidget *container = widgetFactory->containerOfWidget(w: m_parentWidget);
313
314 // ### remove widget here
315 QWidget *parentWidget = w->parentWidget();
316 QDesignerFormEditorInterface *core = m_formWindow->core();
317 QDesignerLayoutDecorationExtension *deco = qt_extension<QDesignerLayoutDecorationExtension*>(manager: core->extensionManager(), object: parentWidget);
318
319 if (deco)
320 deco->removeWidget(widget: w);
321
322 w->setParent(container);
323 w->setGeometry(rc);
324
325 if (showIt)
326 w->show();
327 }
328
329 LayoutInfo::deleteLayout(core: m_formWindow->core(), widget: m_layoutBase);
330
331 if (m_parentWidget != m_layoutBase && !qobject_cast<QMainWindow*>(object: m_layoutBase)) {
332 m_formWindow->unmanageWidget(widget: m_layoutBase);
333 m_layoutBase->hide();
334 } else {
335 QMainWindow *mw = qobject_cast<QMainWindow*>(object: m_formWindow->mainContainer());
336 if (m_layoutBase != m_formWindow->mainContainer() &&
337 (!mw || mw->centralWidget() != m_layoutBase))
338 m_layoutBase->setGeometry(m_oldGeometry);
339 }
340}
341
342void Layout::breakLayout()
343{
344 QHash<QWidget *, QRect> rects;
345 /* Store the geometry of the widgets. The idea is to give the user space
346 * to rearrange them, so, we do a adjustSize() on them, unless they want
347 * to grow (expanding widgets like QTextEdit), in which the geometry is
348 * preserved. Note that historically, geometries were re-applied
349 * only after breaking splitters. */
350 for (QWidget *w : std::as_const(t&: m_widgets)) {
351 const QRect geom = w->geometry();
352 const QSize sizeHint = w->sizeHint();
353 const bool restoreGeometry = sizeHint.isEmpty() || sizeHint.width() > geom.width() || sizeHint.height() > geom.height();
354 rects.insert(key: w, value: restoreGeometry ? w->geometry() : QRect(geom.topLeft(), QSize()));
355 }
356 const QPoint m_layoutBasePos = m_layoutBase->pos();
357 QDesignerWidgetDataBaseInterface *widgetDataBase = m_formWindow->core()->widgetDataBase();
358
359 LayoutInfo::deleteLayout(core: m_formWindow->core(), widget: m_layoutBase);
360
361 const bool needReparent = (m_reparentLayoutWidget && qobject_cast<QLayoutWidget*>(object: m_layoutBase)) ||
362 qobject_cast<QSplitter*>(object: m_layoutBase) ||
363 (!widgetDataBase->isContainer(object: m_layoutBase, resolveName: false) &&
364 m_layoutBase != m_formWindow->mainContainer());
365 const bool add = m_geometries.isEmpty();
366
367 for (auto it = rects.cbegin(), end = rects.cend(); it != end; ++it) {
368 QWidget *w = it.key();
369 if (needReparent) {
370 w->setParent(parent: m_layoutBase->parentWidget(), f: {});
371 w->move(m_layoutBasePos + it.value().topLeft());
372 w->show();
373 }
374
375 const QRect oldGeometry = it.value();
376 if (oldGeometry.isEmpty()) {
377 w->adjustSize();
378 } else {
379 w->resize(oldGeometry.size());
380 }
381
382 if (add)
383 m_geometries.insert(key: w, value: QRect(w->pos(), w->size()));
384 }
385
386 if (needReparent) {
387 m_layoutBase->hide();
388 m_parentWidget = m_layoutBase->parentWidget();
389 m_formWindow->unmanageWidget(widget: m_layoutBase);
390 } else {
391 m_parentWidget = m_layoutBase;
392 }
393 updateWizardLayout(layoutBase: m_layoutBase);
394
395 if (!m_widgets.isEmpty() && m_widgets.first() && m_widgets.first()->isVisibleTo(m_formWindow))
396 m_formWindow->selectWidget(w: m_widgets.first());
397 else
398 m_formWindow->selectWidget(w: m_formWindow);
399}
400
401static QString suggestLayoutName(const char *className)
402{
403 // Legacy
404 if (!qstrcmp(str1: className, str2: "QHBoxLayout"))
405 return u"horizontalLayout"_s;
406 if (!qstrcmp(str1: className, str2: "QVBoxLayout"))
407 return u"verticalLayout"_s;
408 if (!qstrcmp(str1: className, str2: "QGridLayout"))
409 return u"gridLayout"_s;
410
411 return qtify(name: QString::fromUtf8(utf8: className));
412}
413QLayout *Layout::createLayout(int type)
414{
415 Q_ASSERT(m_layoutType != LayoutInfo::HSplitter && m_layoutType != LayoutInfo::VSplitter);
416 QLayout *layout = m_formWindow->core()->widgetFactory()->createLayout(widget: m_layoutBase, layout: nullptr, type);
417 // set a name
418 layout->setObjectName(suggestLayoutName(className: layout->metaObject()->className()));
419 m_formWindow->ensureUniqueObjectName(object: layout);
420 // QLayoutWidget
421 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: m_formWindow->core()->extensionManager(), object: layout);
422 if (sheet && qobject_cast<QLayoutWidget*>(object: m_layoutBase)) {
423 sheet->setProperty(index: sheet->indexOf(name: u"leftMargin"_s), value: 0);
424 sheet->setProperty(index: sheet->indexOf(name: u"topMargin"_s), value: 0);
425 sheet->setProperty(index: sheet->indexOf(name: u"rightMargin"_s), value: 0);
426 sheet->setProperty(index: sheet->indexOf(name: u"bottomMargin"_s), value: 0);
427 }
428 return layout;
429}
430
431void Layout::reparentToLayoutBase(QWidget *w)
432{
433 if (w->parent() != m_layoutBase) {
434 w->setParent(parent: m_layoutBase, f: {});
435 w->move(QPoint(0,0));
436 }
437}
438
439namespace { // within qdesigner_internal
440
441// ----- PositionSortPredicate: Predicate to be usable as LessThan function to sort widgets by position
442class PositionSortPredicate {
443public:
444 PositionSortPredicate(Qt::Orientation orientation) : m_orientation(orientation) {}
445 bool operator()(const QWidget* w1, const QWidget* w2) {
446 return m_orientation == Qt::Horizontal ? w1->x() < w2->x() : w1->y() < w2->y();
447 }
448 private:
449 const Qt::Orientation m_orientation;
450};
451
452// -------- BoxLayout
453class BoxLayout : public Layout
454{
455public:
456 BoxLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb,
457 Qt::Orientation orientation);
458
459 void doLayout() override;
460 void sort() override;
461
462private:
463 const Qt::Orientation m_orientation;
464};
465
466BoxLayout::BoxLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb,
467 Qt::Orientation orientation) :
468 Layout(wl, p, fw, lb, orientation == Qt::Horizontal ? LayoutInfo::HBox : LayoutInfo::VBox),
469 m_orientation(orientation)
470{
471}
472
473void BoxLayout::sort()
474{
475 QWidgetList wl = widgets();
476 std::stable_sort(first: wl.begin(), last: wl.end(), comp: PositionSortPredicate(m_orientation));
477 setWidgets(wl);
478}
479
480void BoxLayout::doLayout()
481{
482 bool needMove, needReparent;
483 if (!prepareLayout(needMove, needReparent))
484 return;
485
486 QBoxLayout *layout = static_cast<QBoxLayout *>(createLayout(type: m_orientation == Qt::Horizontal ? LayoutInfo::HBox : LayoutInfo::VBox));
487
488 QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem.
489
490 for (auto *w : widgets()) {
491 if (needReparent)
492 reparentToLayoutBase(w);
493
494 if (const Spacer *spacer = qobject_cast<const Spacer*>(object: w))
495 layout->addWidget(w, stretch: 0, alignment: spacer->alignment());
496 else
497 layout->addWidget(w);
498 w->show();
499 }
500 finishLayout(needMove, layout);
501}
502
503// -------- SplitterLayout
504class SplitterLayout : public Layout
505{
506public:
507 SplitterLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb,
508 Qt::Orientation orientation);
509
510 void doLayout() override;
511 void sort() override;
512
513private:
514 const Qt::Orientation m_orientation;
515};
516
517SplitterLayout::SplitterLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb,
518 Qt::Orientation orientation) :
519 Layout(wl, p, fw, lb, orientation == Qt::Horizontal ? LayoutInfo::HSplitter : LayoutInfo::VSplitter),
520 m_orientation(orientation)
521{
522}
523
524void SplitterLayout::sort()
525{
526 QWidgetList wl = widgets();
527 std::stable_sort(first: wl.begin(), last: wl.end(), comp: PositionSortPredicate(m_orientation));
528 setWidgets(wl);
529}
530
531void SplitterLayout::doLayout()
532{
533 bool needMove, needReparent;
534 if (!prepareLayout(needMove, needReparent))
535 return;
536
537 QSplitter *splitter = qobject_cast<QSplitter*>(object: layoutBaseWidget());
538 Q_ASSERT(splitter != nullptr);
539
540 for (auto *w : widgets()) {
541 if (needReparent)
542 reparentToLayoutBase(w);
543 splitter->addWidget(widget: w);
544 w->show();
545 }
546
547 splitter->setOrientation(m_orientation);
548 finishLayout(needMove);
549}
550
551// ---------- Grid: Helper for laying out grids
552
553class GridHelper
554{
555 Q_DISABLE_COPY_MOVE(GridHelper);
556public:
557 enum { FormLayoutColumns = 2 };
558
559 enum Mode {
560 GridLayout, // Arbitrary size/supports span
561 FormLayout // 2-column/no span
562 };
563
564 GridHelper(Mode mode);
565 void resize(int nrows, int ncols);
566
567 ~GridHelper();
568
569 QWidget* cell(int row, int col) const { return m_cells[ row * m_ncols + col]; }
570
571 void setCells(const QRect &c, QWidget* w);
572
573 bool empty() const { return !m_nrows || !m_ncols; }
574 int numRows() const { return m_nrows; }
575 int numCols() const { return m_ncols; }
576
577 void simplify();
578 bool locateWidget(QWidget* w, int& row, int& col, int& rowspan, int& colspan) const;
579
580private:
581 void setCell(int row, int col, QWidget* w) { m_cells[ row * m_ncols + col] = w; }
582 void shrink();
583 void reallocFormLayout();
584 int countRow(int r, int c) const;
585 int countCol(int r, int c) const;
586 void setRow(int r, int c, QWidget* w, int count);
587 void setCol(int r, int c, QWidget* w, int count);
588 bool isWidgetStartCol(int c) const;
589 bool isWidgetEndCol(int c) const;
590 bool isWidgetStartRow(int r) const;
591 bool isWidgetEndRow(int r) const;
592 bool isWidgetTopLeft(int r, int c) const;
593 void extendLeft();
594 void extendRight();
595 void extendUp();
596 void extendDown();
597 bool shrinkFormLayoutSpans();
598
599 const Mode m_mode;
600 int m_nrows;
601 int m_ncols;
602
603 QWidget** m_cells; // widget matrix w11, w12, w21...
604};
605
606GridHelper::GridHelper(Mode mode) :
607 m_mode(mode),
608 m_nrows(0),
609 m_ncols(0),
610 m_cells(nullptr)
611{
612}
613
614GridHelper::~GridHelper()
615{
616 delete [] m_cells;
617}
618
619void GridHelper::resize(int nrows, int ncols)
620{
621 delete [] m_cells;
622 m_cells = nullptr;
623 m_nrows = nrows;
624 m_ncols = ncols;
625 if (const int allocSize = m_nrows * m_ncols) {
626 m_cells = new QWidget*[allocSize];
627 std::fill(first: m_cells, last: m_cells + allocSize, value: nullptr);
628 }
629}
630
631void GridHelper::setCells(const QRect &c, QWidget* w)
632{
633 const int bottom = c.top() + c.height();
634 const int width = c.width();
635
636 for (int r = c.top(); r < bottom; r++) {
637 QWidget **pos = m_cells + r * m_ncols + c.left();
638 std::fill(first: pos, last: pos + width, value: w);
639 }
640}
641
642int GridHelper::countRow(int r, int c) const
643{
644 QWidget* w = cell(row: r, col: c);
645 int i = c + 1;
646 while (i < m_ncols && cell(row: r, col: i) == w)
647 i++;
648 return i - c;
649}
650
651int GridHelper::countCol(int r, int c) const
652{
653 QWidget* w = cell(row: r, col: c);
654 int i = r + 1;
655 while (i < m_nrows && cell(row: i, col: c) == w)
656 i++;
657 return i - r;
658}
659
660void GridHelper::setCol(int r, int c, QWidget* w, int count)
661{
662 for (int i = 0; i < count; i++)
663 setCell(row: r + i, col: c, w);
664}
665
666void GridHelper::setRow(int r, int c, QWidget* w, int count)
667{
668 for (int i = 0; i < count; i++)
669 setCell(row: r, col: c + i, w);
670}
671
672bool GridHelper::isWidgetStartCol(int c) const
673{
674 for (int r = 0; r < m_nrows; r++) {
675 if (cell(row: r, col: c) && ((c==0) || (cell(row: r, col: c) != cell(row: r, col: c-1)))) {
676 return true;
677 }
678 }
679 return false;
680}
681
682bool GridHelper::isWidgetEndCol(int c) const
683{
684 for (int r = 0; r < m_nrows; r++) {
685 if (cell(row: r, col: c) && ((c == m_ncols-1) || (cell(row: r, col: c) != cell(row: r, col: c+1))))
686 return true;
687 }
688 return false;
689}
690
691bool GridHelper::isWidgetStartRow(int r) const
692{
693 for ( int c = 0; c < m_ncols; c++) {
694 if (cell(row: r, col: c) && ((r==0) || (cell(row: r, col: c) != cell(row: r-1, col: c))))
695 return true;
696 }
697 return false;
698}
699
700bool GridHelper::isWidgetEndRow(int r) const
701{
702 for (int c = 0; c < m_ncols; c++) {
703 if (cell(row: r, col: c) && ((r == m_nrows-1) || (cell(row: r, col: c) != cell(row: r+1, col: c))))
704 return true;
705 }
706 return false;
707}
708
709
710bool GridHelper::isWidgetTopLeft(int r, int c) const
711{
712 QWidget* w = cell(row: r, col: c);
713 if (!w)
714 return false;
715 return (!r || cell(row: r-1, col: c) != w) && (!c || cell(row: r, col: c-1) != w);
716}
717
718void GridHelper::extendLeft()
719{
720 for (int c = 1; c < m_ncols; c++) {
721 for (int r = 0; r < m_nrows; r++) {
722 QWidget* w = cell(row: r, col: c);
723 if (!w)
724 continue;
725
726 const int cc = countCol(r, c);
727 int stretch = 0;
728 for (int i = c-1; i >= 0; i--) {
729 if (cell(row: r, col: i))
730 break;
731 if (countCol(r, c: i) < cc)
732 break;
733 if (isWidgetEndCol(c: i))
734 break;
735 if (isWidgetStartCol(c: i)) {
736 stretch = c - i;
737 break;
738 }
739 }
740 if (stretch) {
741 for (int i = 0; i < stretch; i++)
742 setCol(r, c: c-i-1, w, count: cc);
743 }
744 }
745 }
746}
747
748
749void GridHelper::extendRight()
750{
751 for (int c = m_ncols - 2; c >= 0; c--) {
752 for (int r = 0; r < m_nrows; r++) {
753 QWidget* w = cell(row: r, col: c);
754 if (!w)
755 continue;
756 const int cc = countCol(r, c);
757 int stretch = 0;
758 for (int i = c+1; i < m_ncols; i++) {
759 if (cell(row: r, col: i))
760 break;
761 if (countCol(r, c: i) < cc)
762 break;
763 if (isWidgetStartCol(c: i))
764 break;
765 if (isWidgetEndCol(c: i)) {
766 stretch = i - c;
767 break;
768 }
769 }
770 if (stretch) {
771 for (int i = 0; i < stretch; i++)
772 setCol(r, c: c+i+1, w, count: cc);
773 }
774 }
775 }
776
777}
778
779void GridHelper::extendUp()
780{
781 for (int r = 1; r < m_nrows; r++) {
782 for (int c = 0; c < m_ncols; c++) {
783 QWidget* w = cell(row: r, col: c);
784 if (!w)
785 continue;
786 const int cr = countRow(r, c);
787 int stretch = 0;
788 for (int i = r-1; i >= 0; i--) {
789 if (cell(row: i, col: c))
790 break;
791 if (countRow(r: i, c) < cr)
792 break;
793 if (isWidgetEndRow(r: i))
794 break;
795 if (isWidgetStartRow(r: i)) {
796 stretch = r - i;
797 break;
798 }
799 }
800 if (stretch) {
801 for (int i = 0; i < stretch; i++)
802 setRow(r: r-i-1, c, w, count: cr);
803 }
804 }
805 }
806}
807
808void GridHelper::extendDown()
809{
810 for (int r = m_nrows - 2; r >= 0; r--) {
811 for (int c = 0; c < m_ncols; c++) {
812 QWidget* w = cell(row: r, col: c);
813 if (!w)
814 continue;
815 const int cr = countRow(r, c);
816 int stretch = 0;
817 for (int i = r+1; i < m_nrows; i++) {
818 if (cell(row: i, col: c))
819 break;
820 if (countRow(r: i, c) < cr)
821 break;
822 if (isWidgetStartRow(r: i))
823 break;
824 if (isWidgetEndRow(r: i)) {
825 stretch = i - r;
826 break;
827 }
828 }
829 if (stretch) {
830 for (int i = 0; i < stretch; i++)
831 setRow(r: r+i+1, c, w, count: cr);
832 }
833 }
834 }
835}
836
837void GridHelper::simplify()
838{
839 switch (m_mode) {
840 case GridLayout:
841 // Grid: Extend all widgets to occupy most space and delete
842 // rows/columns that are not bordering on a widget
843 extendLeft();
844 extendRight();
845 extendUp();
846 extendDown();
847 shrink();
848 break;
849 case FormLayout:
850 // Form: First treat it as a grid to get the same behaviour
851 // regarding spanning and shrinking. Then restrict the span to
852 // the horizontal span possible in the form, simplify again
853 // and spread the widgets over a 2-column layout
854 extendLeft();
855 extendRight();
856 extendUp();
857 extendDown();
858 shrink();
859 if (shrinkFormLayoutSpans())
860 shrink();
861 reallocFormLayout();
862 break;
863 }
864
865}
866
867void GridHelper::shrink()
868{
869 // tick off the occupied cols/rows (bordering on widget edges)
870 QList<bool> columns(m_ncols, false);
871 QList<bool> rows(m_nrows, false);
872
873 for (int c = 0; c < m_ncols; c++)
874 for (int r = 0; r < m_nrows; r++)
875 if (isWidgetTopLeft(r, c))
876 rows[r] = columns[c] = true;
877
878 // remove empty cols/rows
879 const int simplifiedNCols = columns.count(t: true);
880 const int simplifiedNRows = rows.count(t: true);
881 if (simplifiedNCols == m_ncols && simplifiedNRows == m_nrows)
882 return;
883 // reallocate and copy omitting the empty cells
884 QWidget **simplifiedCells = new QWidget*[simplifiedNCols * simplifiedNRows];
885 std::fill(first: simplifiedCells, last: simplifiedCells + simplifiedNCols * simplifiedNRows, value: nullptr);
886 QWidget **simplifiedPtr = simplifiedCells;
887
888 for (int r = 0; r < m_nrows; r++)
889 if (rows[r])
890 for (int c = 0; c < m_ncols; c++)
891 if (columns[c]) {
892 if (QWidget *w = cell(row: r, col: c))
893 *simplifiedPtr = w;
894 simplifiedPtr++;
895 }
896 Q_ASSERT(simplifiedPtr == simplifiedCells + simplifiedNCols * simplifiedNRows);
897 delete [] m_cells;
898 m_cells = simplifiedCells;
899 m_nrows = simplifiedNRows;
900 m_ncols = simplifiedNCols;
901}
902
903bool GridHelper::shrinkFormLayoutSpans()
904{
905 bool shrunk = false;
906 using WidgetSet = QSet<QWidget *>;
907 // Determine unique set of widgets
908 WidgetSet widgets;
909 QWidget **end = m_cells + m_ncols * m_nrows;
910 for (QWidget **wptr = m_cells; wptr < end; wptr++)
911 if (QWidget *w = *wptr)
912 widgets.insert(value: w);
913 // Restrict the widget span: max horizontal span at column 0: 2, anything else: 1
914 const int maxRowSpan = 1;
915 for (auto *w : std::as_const(t&: widgets)) {
916 int row, col, rowspan, colspan;
917 if (!locateWidget(w, row, col, rowspan, colspan)) {
918 qDebug(msg: "ooops, widget '%s' does not fit in layout", w->objectName().toUtf8().constData());
919 row = col = rowspan = colspan = 0;
920 }
921 const int maxColSpan = col == 0 ? 2 : 1;
922 const int newColSpan = qMin(a: colspan, b: maxColSpan);
923 const int newRowSpan = qMin(a: rowspan, b: maxRowSpan);
924 if (newColSpan != colspan || newRowSpan != rowspan) {
925 // in case like this:
926 // W1 W1
927 // W1 W2
928 // do:
929 // W1 0
930 // 0 W2
931 for (int i = row; i < row + rowspan - 1; i++)
932 for (int j = col; j < col + colspan - 1; j++)
933 if (i > row + newColSpan - 1 || j > col + newRowSpan - 1)
934 if (cell(row: i, col: j) == w)
935 setCell(row: i, col: j, w: nullptr);
936 shrunk = true;
937 }
938 }
939 return shrunk;
940}
941
942void GridHelper::reallocFormLayout()
943{
944 // Columns matching? -> happy!
945 if (m_ncols == FormLayoutColumns)
946 return;
947
948 // If there are offset columns (starting past the field column),
949 // move them to the left and squeeze them. This also prevents the
950 // following reallocation from creating empty form rows.
951 int pastRightWidgetCount = 0;
952 if (m_ncols > FormLayoutColumns) {
953 for (int r = 0; r < m_nrows; r++) {
954 // Try to find a column where the form columns are empty and
955 // there are widgets further to the right.
956 if (cell(row: r, col: 0) == nullptr && cell(row: r, col: 1) == nullptr) {
957 int sourceCol = FormLayoutColumns;
958 QWidget *firstWidget = nullptr;
959 for ( ; sourceCol < m_ncols; sourceCol++)
960 if (QWidget *w = cell(row: r, col: sourceCol)) {
961 firstWidget = w;
962 break;
963 }
964 if (firstWidget) {
965 // Move/squeeze. Copy to beginning of column if it is a label, else field
966 int targetCol = qobject_cast<QLabel*>(object: firstWidget) ? 0 : 1;
967 for ( ; sourceCol < m_ncols; sourceCol++)
968 if (QWidget *w = cell(row: r, col: sourceCol))
969 setCell(row: r, col: targetCol++, w);
970 // Pad with zero
971 for ( ; targetCol < m_ncols; targetCol++)
972 setCell(row: r, col: targetCol, w: nullptr);
973 }
974 }
975 // Any protruding widgets left on that row?
976 for (int c = FormLayoutColumns; c < m_ncols; c++)
977 if (cell(row: r, col: c))
978 pastRightWidgetCount++;
979 }
980 }
981 // Reallocate with 2 columns. Just insert the protruding ones as fields.
982 const int formNRows = m_nrows + pastRightWidgetCount;
983 QWidget **formCells = new QWidget*[FormLayoutColumns * formNRows];
984 std::fill(first: formCells, last: formCells + FormLayoutColumns * formNRows, value: nullptr);
985 QWidget **formPtr = formCells;
986 const int matchingColumns = qMin(a: m_ncols, b: static_cast<int>(FormLayoutColumns));
987 for (int r = 0; r < m_nrows; r++) {
988 int c = 0;
989 for ( ; c < matchingColumns; c++) // Just copy over matching columns
990 *formPtr++ = cell(row: r, col: c);
991 formPtr += FormLayoutColumns - matchingColumns; // In case old format was 1 column
992 // protruding widgets: Insert as single-field rows
993 for ( ; c < m_ncols; c++)
994 if (QWidget *w = cell(row: r, col: c)) {
995 formPtr++;
996 *formPtr++ = w;
997 }
998 }
999 Q_ASSERT(formPtr == formCells + FormLayoutColumns * formNRows);
1000 delete [] m_cells;
1001 m_cells = formCells;
1002 m_nrows = formNRows;
1003 m_ncols = FormLayoutColumns;
1004}
1005
1006bool GridHelper::locateWidget(QWidget *w, int &row, int &col, int &rowspan, int &colspan) const
1007{
1008 const int end = m_nrows * m_ncols;
1009 const int startIndex = std::find(first: m_cells, last: m_cells + end, val: w) - m_cells;
1010 if (startIndex == end)
1011 return false;
1012
1013 row = startIndex / m_ncols;
1014 col = startIndex % m_ncols;
1015 for (rowspan = 1; row + rowspan < m_nrows && cell(row: row + rowspan, col) == w; rowspan++) {}
1016 for (colspan = 1; col + colspan < m_ncols && cell(row, col: col + colspan) == w; colspan++) {}
1017 return true;
1018}
1019
1020// QGridLayout/QFormLayout Helpers: get item position/add item (overloads to make templates work)
1021
1022void addWidgetToGrid(QGridLayout *lt, QWidget * widget, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment)
1023{
1024 lt->addWidget(widget, row, column, rowSpan, columnSpan, alignment);
1025}
1026
1027inline void addWidgetToGrid(QFormLayout *lt, QWidget * widget, int row, int column, int, int columnSpan, Qt::Alignment)
1028{
1029 formLayoutAddWidget(formLayout: lt, w: widget, r: QRect(column, row, columnSpan, 1), insert: false);
1030}
1031
1032// ----------- Base template for grid like layouts
1033template <class GridLikeLayout, int LayoutType, int GridMode>
1034class GridLayout : public Layout
1035{
1036public:
1037 GridLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb);
1038
1039 void doLayout() override;
1040 void sort() override { setWidgets(buildGrid(widgets())); }
1041
1042protected:
1043 QWidgetList buildGrid(const QWidgetList &);
1044 GridHelper m_grid;
1045};
1046
1047template <class GridLikeLayout, int LayoutType, int GridMode>
1048GridLayout<GridLikeLayout, LayoutType, GridMode>::GridLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb) :
1049 Layout(wl, p, fw, lb, LayoutInfo::Grid),
1050 m_grid(static_cast<GridHelper::Mode>(GridMode))
1051{
1052}
1053
1054template <class GridLikeLayout, int LayoutType, int GridMode>
1055void GridLayout<GridLikeLayout, LayoutType, GridMode>::doLayout()
1056{
1057 bool needMove, needReparent;
1058 if (!prepareLayout(needMove, needReparent))
1059 return;
1060
1061 GridLikeLayout *layout = static_cast<GridLikeLayout *>(createLayout(LayoutType));
1062
1063 if (!m_grid.empty())
1064 sort();
1065
1066 QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem.
1067
1068 for (auto *w : widgets()) {
1069 int r = 0, c = 0, rs = 0, cs = 0;
1070
1071 if (m_grid.locateWidget(w, row&: r, col&: c, rowspan&: rs, colspan&: cs)) {
1072 if (needReparent)
1073 reparentToLayoutBase(w);
1074
1075 Qt::Alignment alignment;
1076 if (const Spacer *spacer = qobject_cast<const Spacer*>(w))
1077 alignment = spacer->alignment();
1078
1079 addWidgetToGrid(layout, w, r, c, rs, cs, alignment);
1080
1081 w->show();
1082 } else {
1083 qDebug("ooops, widget '%s' does not fit in layout", w->objectName().toUtf8().constData());
1084 }
1085 }
1086
1087 QLayoutSupport::createEmptyCells(layout);
1088
1089 finishLayout(needMove, layout);
1090}
1091
1092// Remove duplicate entries (Remove next, if equal to current)
1093void removeIntVecDuplicates(QList<int> &v)
1094{
1095 if (v.size() < 2)
1096 return;
1097
1098 for (auto current = v.begin() ; (current != v.end()) && ((current + 1) != v.end()) ; )
1099 if ( *current == *(current+1) )
1100 v.erase(pos: current+1);
1101 else
1102 ++current;
1103}
1104
1105// Ensure a non-zero size for a widget geometry (squeezed spacers)
1106inline QRect expandGeometry(const QRect &rect)
1107{
1108 return rect.isEmpty() ? QRect(rect.topLeft(), rect.size().expandedTo(otherSize: QSize(1, 1))) : rect;
1109}
1110
1111template <class GridLikeLayout, int LayoutType, int GridMode>
1112QWidgetList GridLayout<GridLikeLayout, LayoutType, GridMode>::buildGrid(const QWidgetList &widgetList)
1113{
1114 if (widgetList.isEmpty())
1115 return QWidgetList();
1116
1117 // Pixel to cell conversion:
1118 // By keeping a list of start'n'stop values (x & y) for each widget,
1119 // it is possible to create a very small grid of cells to represent
1120 // the widget layout.
1121 // -----------------------------------------------------------------
1122
1123 // We need a list of both start and stop values for x- & y-axis
1124 const auto widgetCount = widgetList.size();
1125 QList<int> x( widgetCount * 2 );
1126 QList<int> y( widgetCount * 2 );
1127
1128 // Using push_back would look nicer, but operator[] is much faster
1129 qsizetype index = 0;
1130 for (const auto *w : widgetList) {
1131 const QRect widgetPos = expandGeometry(rect: w->geometry());
1132 x[index] = widgetPos.left();
1133 x[index+1] = widgetPos.right();
1134 y[index] = widgetPos.top();
1135 y[index+1] = widgetPos.bottom();
1136 index += 2;
1137 }
1138
1139 std::sort(first: x.begin(), last: x.end());
1140 std::sort(first: y.begin(), last: y.end());
1141
1142 // Remove duplicate x entries (Remove next, if equal to current)
1143 removeIntVecDuplicates(v&: x);
1144 removeIntVecDuplicates(v&: y);
1145
1146 // Note that left == right and top == bottom for size 1 items; reserve
1147 // enough space
1148 m_grid.resize(nrows: y.size(), ncols: x.size());
1149
1150 for (auto *w : widgetList) {
1151 // Mark the cells in the grid that contains a widget
1152 const QRect widgetPos = expandGeometry(rect: w->geometry());
1153 QRect c(0, 0, 0, 0); // rect of columns/rows
1154
1155 // From left til right (not including)
1156 const int leftIdx = x.indexOf(t: widgetPos.left());
1157 Q_ASSERT(leftIdx != -1);
1158 c.setLeft(leftIdx);
1159 c.setRight(leftIdx);
1160 for (qsizetype cw = leftIdx; cw < x.size(); ++cw)
1161 if (x.at(i: cw) < widgetPos.right())
1162 c.setRight(cw);
1163 else
1164 break;
1165 // From top til bottom (not including)
1166 const int topIdx = y.indexOf(t: widgetPos.top());
1167 Q_ASSERT(topIdx != -1);
1168 c.setTop(topIdx);
1169 c.setBottom(topIdx);
1170 for (qsizetype ch = topIdx; ch < y.size(); ++ch)
1171 if (y.at(i: ch) < widgetPos.bottom())
1172 c.setBottom(ch);
1173 else
1174 break;
1175 m_grid.setCells(c, w); // Mark cellblock
1176 }
1177
1178 m_grid.simplify();
1179
1180 QWidgetList ordered;
1181 for (int i = 0; i < m_grid.numRows(); i++)
1182 for (int j = 0; j < m_grid.numCols(); j++) {
1183 QWidget *w = m_grid.cell(row: i, col: j);
1184 if (w && !ordered.contains(t: w))
1185 ordered.append(t: w);
1186 }
1187 return ordered;
1188}
1189} // anonymous
1190
1191Layout* Layout::createLayout(const QWidgetList &widgets, QWidget *parentWidget,
1192 QDesignerFormWindowInterface *fw,
1193 QWidget *layoutBase, LayoutInfo::Type layoutType)
1194{
1195 switch (layoutType) {
1196 case LayoutInfo::Grid:
1197 return new GridLayout<QGridLayout, LayoutInfo::Grid, GridHelper::GridLayout>(widgets, parentWidget, fw, layoutBase);
1198 case LayoutInfo::HBox:
1199 case LayoutInfo::VBox: {
1200 const Qt::Orientation orientation = layoutType == LayoutInfo::HBox ? Qt::Horizontal : Qt::Vertical;
1201 return new BoxLayout(widgets, parentWidget, fw, layoutBase, orientation);
1202 }
1203 case LayoutInfo::HSplitter:
1204 case LayoutInfo::VSplitter: {
1205 const Qt::Orientation orientation = layoutType == LayoutInfo::HSplitter ? Qt::Horizontal : Qt::Vertical;
1206 return new SplitterLayout(widgets, parentWidget, fw, layoutBase, orientation);
1207 }
1208 case LayoutInfo::Form:
1209 return new GridLayout<QFormLayout, LayoutInfo::Form, GridHelper::FormLayout>(widgets, parentWidget, fw, layoutBase);
1210 default:
1211 break;
1212 }
1213 Q_ASSERT(0);
1214 return nullptr;
1215}
1216
1217} // namespace qdesigner_internal
1218
1219QT_END_NAMESPACE
1220

source code of qttools/src/designer/src/lib/shared/layout.cpp