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

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