// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "layout_p.h" #include "layoutdecoration.h" #include "qdesigner_utils_p.h" #include "qdesigner_widgetitem_p.h" #include "qlayout_widget_p.h" #include "spacer_widget_p.h" #include "widgetfactory_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; namespace qdesigner_internal { /* The wizard has a policy of setting a size policy of its external children * according to the page being expanding or not (in the latter case, the * page will be pushed to the top). When setting/breaking layouts, this needs * to be updated, which happens via a fake style change event. */ void updateWizardLayout(QWidget *layoutBase); class FriendlyWizardPage : public QWizardPage { friend void updateWizardLayout(QWidget *); }; void updateWizardLayout(QWidget *layoutBase) { if (QWizardPage *wizardPage = qobject_cast(layoutBase)) if (QWizard *wizard = static_cast(wizardPage)->wizard()) { QEvent event(QEvent::StyleChange); QApplication::sendEvent(wizard, &event); } } /*! \class qdesigner_internal::Layout \brief Baseclass for layouting widgets in the Designer (Helper for Layout commands) \internal Classes derived from this abstract base class are used for layouting operations in the Designer (creating/breaking layouts). Instances live in the Layout/BreakLayout commands. */ /*! \a p specifies the parent of the layoutBase \a lb. The parent might be changed in setup(). If the layoutBase is a container, the parent and the layoutBase are the same. Also they always have to be a widget known to the designer (e.g. in the case of the tabwidget parent and layoutBase are the tabwidget and not the page which actually gets laid out. For actual usage the correct widget is found later by Layout.) */ Layout::Layout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, LayoutInfo::Type layoutType) : m_widgets(wl), m_parentWidget(p), m_layoutBase(lb), m_formWindow(fw), m_layoutType(layoutType), m_reparentLayoutWidget(true), m_isBreak(false) { if (m_layoutBase) m_oldGeometry = m_layoutBase->geometry(); } Layout::~Layout() = default; /*! The widget list we got in the constructor might contain too much widgets (like widgets with different parents, already laid out widgets, etc.). Here we set up the list and so the only the "best" widgets get laid out. */ void Layout::setup() { m_startPoint = QPoint(32767, 32767); // Go through all widgets of the list we got. As we can only // layout widgets which have the same parent, we first do some // sorting which means create a list for each parent containing // its child here. After that we keep working on the list of // children which has the most entries. // Widgets which are already laid out are thrown away here too QMultiMap lists; for (QWidget *w : std::as_const(m_widgets)) { QWidget *p = w->parentWidget(); if (p && LayoutInfo::layoutType(m_formWindow->core(), p) != LayoutInfo::NoLayout && m_formWindow->core()->metaDataBase()->item(p->layout()) != nullptr) continue; lists.insert(p, w); } QWidgetList lastList; const QWidgetList &parents = lists.keys(); for (QWidget *p : parents) { if (lists.count(p) > lastList.size()) lastList = lists.values(p); } // If we found no list (because no widget did fit at all) or the // best list has only one entry and we do not layout a container, // we leave here. QDesignerWidgetDataBaseInterface *widgetDataBase = m_formWindow->core()->widgetDataBase(); if (lastList.size() < 2 && (!m_layoutBase || (!widgetDataBase->isContainer(m_layoutBase, false) && m_layoutBase != m_formWindow->mainContainer())) ) { m_widgets.clear(); m_startPoint = QPoint(0, 0); return; } // Now we have a new and clean widget list, which makes sense // to layout m_widgets = lastList; // Also use the only correct parent later, so store it Q_ASSERT(m_widgets.isEmpty() == false); m_parentWidget = m_formWindow->core()->widgetFactory()->widgetOfContainer(m_widgets.first()->parentWidget()); // Now calculate the position where the layout-meta-widget should // be placed and connect to widgetDestroyed() signals of the // widgets to get informed if one gets deleted to be able to // handle that and do not crash in this case for (QWidget *w : std::as_const(m_widgets)) { connect(w, &QObject::destroyed, this, &Layout::widgetDestroyed); m_startPoint = QPoint(qMin(m_startPoint.x(), w->x()), qMin(m_startPoint.y(), w->y())); const QRect rc(w->geometry()); m_geometries.insert(w, rc); // Change the Z-order, as saving/loading uses the Z-order for // writing/creating widgets and this has to be the same as in // the layout. Else saving + loading will give different results w->raise(); } sort(); } void Layout::widgetDestroyed() { if (QWidget *w = qobject_cast(sender())) { m_widgets.removeAt(m_widgets.indexOf(w)); m_geometries.remove(w); } } bool Layout::prepareLayout(bool &needMove, bool &needReparent) { for (QWidget *widget : std::as_const(m_widgets)) widget->raise(); needMove = !m_layoutBase; needReparent = needMove || (m_reparentLayoutWidget && qobject_cast(m_layoutBase)) || qobject_cast(m_layoutBase); QDesignerWidgetFactoryInterface *widgetFactory = m_formWindow->core()->widgetFactory(); QDesignerMetaDataBaseInterface *metaDataBase = m_formWindow->core()->metaDataBase(); if (m_layoutBase == nullptr) { const bool useSplitter = m_layoutType == LayoutInfo::HSplitter || m_layoutType == LayoutInfo::VSplitter; const QString baseWidgetClassName = useSplitter ? u"QSplitter"_s : u"QLayoutWidget"_s; m_layoutBase = widgetFactory->createWidget(baseWidgetClassName, widgetFactory->containerOfWidget(m_parentWidget)); if (useSplitter) { m_layoutBase->setObjectName(u"splitter"_s); m_formWindow->ensureUniqueObjectName(m_layoutBase); } } else { LayoutInfo::deleteLayout(m_formWindow->core(), m_layoutBase); } metaDataBase->add(m_layoutBase); Q_ASSERT(m_layoutBase->layout() == nullptr || metaDataBase->item(m_layoutBase->layout()) == nullptr); return true; } static bool isMainContainer(QDesignerFormWindowInterface *fw, const QWidget *w) { return w && (w == fw || w == fw->mainContainer()); } static bool isPageOfContainerWidget(QDesignerFormWindowInterface *fw, QWidget *widget) { QDesignerContainerExtension *c = qt_extension( fw->core()->extensionManager(), widget->parentWidget()); if (c != nullptr) { for (int i = 0; icount(); ++i) { if (widget == c->widget(i)) return true; } } return false; } void Layout::finishLayout(bool needMove, QLayout *layout) { if (m_parentWidget == m_layoutBase) { QWidget *widget = m_layoutBase; m_oldGeometry = widget->geometry(); bool done = false; while (!isMainContainer(m_formWindow, widget) && !done) { if (!m_formWindow->isManaged(widget)) { widget = widget->parentWidget(); continue; } if (LayoutInfo::isWidgetLaidout(m_formWindow->core(), widget)) { widget = widget->parentWidget(); continue; } if (isPageOfContainerWidget(m_formWindow, widget)) { widget = widget->parentWidget(); continue; } if (widget->parentWidget()) { QScrollArea *area = qobject_cast(widget->parentWidget()->parentWidget()); if (area && area->widget() == widget) { widget = area; continue; } } done = true; } updateWizardLayout(m_layoutBase); QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); // We don't want to resize the form window if (!Utils::isCentralWidget(m_formWindow, widget)) widget->adjustSize(); return; } if (needMove) m_layoutBase->move(m_startPoint); const QRect g(m_layoutBase->pos(), m_layoutBase->size()); if (LayoutInfo::layoutType(m_formWindow->core(), m_layoutBase->parentWidget()) == LayoutInfo::NoLayout && !m_isBreak) m_layoutBase->adjustSize(); else if (m_isBreak) m_layoutBase->setGeometry(m_oldGeometry); m_oldGeometry = g; if (layout) layout->invalidate(); m_layoutBase->show(); if (qobject_cast(m_layoutBase) || qobject_cast(m_layoutBase)) { m_formWindow->clearSelection(false); m_formWindow->manageWidget(m_layoutBase); m_formWindow->selectWidget(m_layoutBase); } } void Layout::undoLayout() { if (m_widgets.isEmpty()) return; m_formWindow->selectWidget(m_layoutBase, false); QDesignerWidgetFactoryInterface *widgetFactory = m_formWindow->core()->widgetFactory(); for (auto it = m_geometries.cbegin(), end = m_geometries.cend(); it != end; ++it) { if (!it.key()) continue; QWidget* w = it.key(); const QRect rc = it.value(); const bool showIt = w->isVisibleTo(m_formWindow); QWidget *container = widgetFactory->containerOfWidget(m_parentWidget); // ### remove widget here QWidget *parentWidget = w->parentWidget(); QDesignerFormEditorInterface *core = m_formWindow->core(); QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), parentWidget); if (deco) deco->removeWidget(w); w->setParent(container); w->setGeometry(rc); if (showIt) w->show(); } LayoutInfo::deleteLayout(m_formWindow->core(), m_layoutBase); if (m_parentWidget != m_layoutBase && !qobject_cast(m_layoutBase)) { m_formWindow->unmanageWidget(m_layoutBase); m_layoutBase->hide(); } else { QMainWindow *mw = qobject_cast(m_formWindow->mainContainer()); if (m_layoutBase != m_formWindow->mainContainer() && (!mw || mw->centralWidget() != m_layoutBase)) m_layoutBase->setGeometry(m_oldGeometry); } } void Layout::breakLayout() { QHash rects; /* Store the geometry of the widgets. The idea is to give the user space * to rearrange them, so, we do a adjustSize() on them, unless they want * to grow (expanding widgets like QTextEdit), in which the geometry is * preserved. Note that historically, geometries were re-applied * only after breaking splitters. */ for (QWidget *w : std::as_const(m_widgets)) { const QRect geom = w->geometry(); const QSize sizeHint = w->sizeHint(); const bool restoreGeometry = sizeHint.isEmpty() || sizeHint.width() > geom.width() || sizeHint.height() > geom.height(); rects.insert(w, restoreGeometry ? w->geometry() : QRect(geom.topLeft(), QSize())); } const QPoint m_layoutBasePos = m_layoutBase->pos(); QDesignerWidgetDataBaseInterface *widgetDataBase = m_formWindow->core()->widgetDataBase(); LayoutInfo::deleteLayout(m_formWindow->core(), m_layoutBase); const bool needReparent = (m_reparentLayoutWidget && qobject_cast(m_layoutBase)) || qobject_cast(m_layoutBase) || (!widgetDataBase->isContainer(m_layoutBase, false) && m_layoutBase != m_formWindow->mainContainer()); const bool add = m_geometries.isEmpty(); for (auto it = rects.cbegin(), end = rects.cend(); it != end; ++it) { QWidget *w = it.key(); if (needReparent) { w->setParent(m_layoutBase->parentWidget(), {}); w->move(m_layoutBasePos + it.value().topLeft()); w->show(); } const QRect oldGeometry = it.value(); if (oldGeometry.isEmpty()) { w->adjustSize(); } else { w->resize(oldGeometry.size()); } if (add) m_geometries.insert(w, QRect(w->pos(), w->size())); } if (needReparent) { m_layoutBase->hide(); m_parentWidget = m_layoutBase->parentWidget(); m_formWindow->unmanageWidget(m_layoutBase); } else { m_parentWidget = m_layoutBase; } updateWizardLayout(m_layoutBase); if (!m_widgets.isEmpty() && m_widgets.first() && m_widgets.first()->isVisibleTo(m_formWindow)) m_formWindow->selectWidget(m_widgets.first()); else m_formWindow->selectWidget(m_formWindow); } static QString suggestLayoutName(const char *className) { // Legacy if (!qstrcmp(className, "QHBoxLayout")) return u"horizontalLayout"_s; if (!qstrcmp(className, "QVBoxLayout")) return u"verticalLayout"_s; if (!qstrcmp(className, "QGridLayout")) return u"gridLayout"_s; return qtify(QString::fromUtf8(className)); } QLayout *Layout::createLayout(int type) { Q_ASSERT(m_layoutType != LayoutInfo::HSplitter && m_layoutType != LayoutInfo::VSplitter); QLayout *layout = m_formWindow->core()->widgetFactory()->createLayout(m_layoutBase, nullptr, type); // set a name layout->setObjectName(suggestLayoutName(layout->metaObject()->className())); m_formWindow->ensureUniqueObjectName(layout); // QLayoutWidget QDesignerPropertySheetExtension *sheet = qt_extension(m_formWindow->core()->extensionManager(), layout); if (sheet && qobject_cast(m_layoutBase)) { sheet->setProperty(sheet->indexOf(u"leftMargin"_s), 0); sheet->setProperty(sheet->indexOf(u"topMargin"_s), 0); sheet->setProperty(sheet->indexOf(u"rightMargin"_s), 0); sheet->setProperty(sheet->indexOf(u"bottomMargin"_s), 0); } return layout; } void Layout::reparentToLayoutBase(QWidget *w) { if (w->parent() != m_layoutBase) { w->setParent(m_layoutBase, {}); w->move(QPoint(0,0)); } } namespace { // within qdesigner_internal // ----- PositionSortPredicate: Predicate to be usable as LessThan function to sort widgets by position class PositionSortPredicate { public: PositionSortPredicate(Qt::Orientation orientation) : m_orientation(orientation) {} bool operator()(const QWidget* w1, const QWidget* w2) { return m_orientation == Qt::Horizontal ? w1->x() < w2->x() : w1->y() < w2->y(); } private: const Qt::Orientation m_orientation; }; // -------- BoxLayout class BoxLayout : public Layout { public: BoxLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, Qt::Orientation orientation); void doLayout() override; void sort() override; private: const Qt::Orientation m_orientation; }; BoxLayout::BoxLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, Qt::Orientation orientation) : Layout(wl, p, fw, lb, orientation == Qt::Horizontal ? LayoutInfo::HBox : LayoutInfo::VBox), m_orientation(orientation) { } void BoxLayout::sort() { QWidgetList wl = widgets(); std::stable_sort(wl.begin(), wl.end(), PositionSortPredicate(m_orientation)); setWidgets(wl); } void BoxLayout::doLayout() { bool needMove, needReparent; if (!prepareLayout(needMove, needReparent)) return; QBoxLayout *layout = static_cast(createLayout(m_orientation == Qt::Horizontal ? LayoutInfo::HBox : LayoutInfo::VBox)); QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. for (auto *w : widgets()) { if (needReparent) reparentToLayoutBase(w); if (const Spacer *spacer = qobject_cast(w)) layout->addWidget(w, 0, spacer->alignment()); else layout->addWidget(w); w->show(); } finishLayout(needMove, layout); } // -------- SplitterLayout class SplitterLayout : public Layout { public: SplitterLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, Qt::Orientation orientation); void doLayout() override; void sort() override; private: const Qt::Orientation m_orientation; }; SplitterLayout::SplitterLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, Qt::Orientation orientation) : Layout(wl, p, fw, lb, orientation == Qt::Horizontal ? LayoutInfo::HSplitter : LayoutInfo::VSplitter), m_orientation(orientation) { } void SplitterLayout::sort() { QWidgetList wl = widgets(); std::stable_sort(wl.begin(), wl.end(), PositionSortPredicate(m_orientation)); setWidgets(wl); } void SplitterLayout::doLayout() { bool needMove, needReparent; if (!prepareLayout(needMove, needReparent)) return; QSplitter *splitter = qobject_cast(layoutBaseWidget()); Q_ASSERT(splitter != nullptr); for (auto *w : widgets()) { if (needReparent) reparentToLayoutBase(w); splitter->addWidget(w); w->show(); } splitter->setOrientation(m_orientation); finishLayout(needMove); } // ---------- Grid: Helper for laying out grids class GridHelper { Q_DISABLE_COPY_MOVE(GridHelper); public: enum { FormLayoutColumns = 2 }; enum Mode { GridLayout, // Arbitrary size/supports span FormLayout // 2-column/no span }; GridHelper(Mode mode); void resize(int nrows, int ncols); ~GridHelper(); QWidget* cell(int row, int col) const { return m_cells[ row * m_ncols + col]; } void setCells(const QRect &c, QWidget* w); bool empty() const { return !m_nrows || !m_ncols; } int numRows() const { return m_nrows; } int numCols() const { return m_ncols; } void simplify(); bool locateWidget(QWidget* w, int& row, int& col, int& rowspan, int& colspan) const; private: void setCell(int row, int col, QWidget* w) { m_cells[ row * m_ncols + col] = w; } void shrink(); void reallocFormLayout(); int countRow(int r, int c) const; int countCol(int r, int c) const; void setRow(int r, int c, QWidget* w, int count); void setCol(int r, int c, QWidget* w, int count); bool isWidgetStartCol(int c) const; bool isWidgetEndCol(int c) const; bool isWidgetStartRow(int r) const; bool isWidgetEndRow(int r) const; bool isWidgetTopLeft(int r, int c) const; void extendLeft(); void extendRight(); void extendUp(); void extendDown(); bool shrinkFormLayoutSpans(); const Mode m_mode; int m_nrows; int m_ncols; QWidget** m_cells; // widget matrix w11, w12, w21... }; GridHelper::GridHelper(Mode mode) : m_mode(mode), m_nrows(0), m_ncols(0), m_cells(nullptr) { } GridHelper::~GridHelper() { delete [] m_cells; } void GridHelper::resize(int nrows, int ncols) { delete [] m_cells; m_cells = nullptr; m_nrows = nrows; m_ncols = ncols; if (const int allocSize = m_nrows * m_ncols) { m_cells = new QWidget*[allocSize]; std::fill(m_cells, m_cells + allocSize, nullptr); } } void GridHelper::setCells(const QRect &c, QWidget* w) { const int bottom = c.top() + c.height(); const int width = c.width(); for (int r = c.top(); r < bottom; r++) { QWidget **pos = m_cells + r * m_ncols + c.left(); std::fill(pos, pos + width, w); } } int GridHelper::countRow(int r, int c) const { QWidget* w = cell(r, c); int i = c + 1; while (i < m_ncols && cell(r, i) == w) i++; return i - c; } int GridHelper::countCol(int r, int c) const { QWidget* w = cell(r, c); int i = r + 1; while (i < m_nrows && cell(i, c) == w) i++; return i - r; } void GridHelper::setCol(int r, int c, QWidget* w, int count) { for (int i = 0; i < count; i++) setCell(r + i, c, w); } void GridHelper::setRow(int r, int c, QWidget* w, int count) { for (int i = 0; i < count; i++) setCell(r, c + i, w); } bool GridHelper::isWidgetStartCol(int c) const { for (int r = 0; r < m_nrows; r++) { if (cell(r, c) && ((c==0) || (cell(r, c) != cell(r, c-1)))) { return true; } } return false; } bool GridHelper::isWidgetEndCol(int c) const { for (int r = 0; r < m_nrows; r++) { if (cell(r, c) && ((c == m_ncols-1) || (cell(r, c) != cell(r, c+1)))) return true; } return false; } bool GridHelper::isWidgetStartRow(int r) const { for ( int c = 0; c < m_ncols; c++) { if (cell(r, c) && ((r==0) || (cell(r, c) != cell(r-1, c)))) return true; } return false; } bool GridHelper::isWidgetEndRow(int r) const { for (int c = 0; c < m_ncols; c++) { if (cell(r, c) && ((r == m_nrows-1) || (cell(r, c) != cell(r+1, c)))) return true; } return false; } bool GridHelper::isWidgetTopLeft(int r, int c) const { QWidget* w = cell(r, c); if (!w) return false; return (!r || cell(r-1, c) != w) && (!c || cell(r, c-1) != w); } void GridHelper::extendLeft() { for (int c = 1; c < m_ncols; c++) { for (int r = 0; r < m_nrows; r++) { QWidget* w = cell(r, c); if (!w) continue; const int cc = countCol(r, c); int stretch = 0; for (int i = c-1; i >= 0; i--) { if (cell(r, i)) break; if (countCol(r, i) < cc) break; if (isWidgetEndCol(i)) break; if (isWidgetStartCol(i)) { stretch = c - i; break; } } if (stretch) { for (int i = 0; i < stretch; i++) setCol(r, c-i-1, w, cc); } } } } void GridHelper::extendRight() { for (int c = m_ncols - 2; c >= 0; c--) { for (int r = 0; r < m_nrows; r++) { QWidget* w = cell(r, c); if (!w) continue; const int cc = countCol(r, c); int stretch = 0; for (int i = c+1; i < m_ncols; i++) { if (cell(r, i)) break; if (countCol(r, i) < cc) break; if (isWidgetStartCol(i)) break; if (isWidgetEndCol(i)) { stretch = i - c; break; } } if (stretch) { for (int i = 0; i < stretch; i++) setCol(r, c+i+1, w, cc); } } } } void GridHelper::extendUp() { for (int r = 1; r < m_nrows; r++) { for (int c = 0; c < m_ncols; c++) { QWidget* w = cell(r, c); if (!w) continue; const int cr = countRow(r, c); int stretch = 0; for (int i = r-1; i >= 0; i--) { if (cell(i, c)) break; if (countRow(i, c) < cr) break; if (isWidgetEndRow(i)) break; if (isWidgetStartRow(i)) { stretch = r - i; break; } } if (stretch) { for (int i = 0; i < stretch; i++) setRow(r-i-1, c, w, cr); } } } } void GridHelper::extendDown() { for (int r = m_nrows - 2; r >= 0; r--) { for (int c = 0; c < m_ncols; c++) { QWidget* w = cell(r, c); if (!w) continue; const int cr = countRow(r, c); int stretch = 0; for (int i = r+1; i < m_nrows; i++) { if (cell(i, c)) break; if (countRow(i, c) < cr) break; if (isWidgetStartRow(i)) break; if (isWidgetEndRow(i)) { stretch = i - r; break; } } if (stretch) { for (int i = 0; i < stretch; i++) setRow(r+i+1, c, w, cr); } } } } void GridHelper::simplify() { switch (m_mode) { case GridLayout: // Grid: Extend all widgets to occupy most space and delete // rows/columns that are not bordering on a widget extendLeft(); extendRight(); extendUp(); extendDown(); shrink(); break; case FormLayout: // Form: First treat it as a grid to get the same behaviour // regarding spanning and shrinking. Then restrict the span to // the horizontal span possible in the form, simplify again // and spread the widgets over a 2-column layout extendLeft(); extendRight(); extendUp(); extendDown(); shrink(); if (shrinkFormLayoutSpans()) shrink(); reallocFormLayout(); break; } } void GridHelper::shrink() { // tick off the occupied cols/rows (bordering on widget edges) QList columns(m_ncols, false); QList rows(m_nrows, false); for (int c = 0; c < m_ncols; c++) for (int r = 0; r < m_nrows; r++) if (isWidgetTopLeft(r, c)) rows[r] = columns[c] = true; // remove empty cols/rows const int simplifiedNCols = columns.count(true); const int simplifiedNRows = rows.count(true); if (simplifiedNCols == m_ncols && simplifiedNRows == m_nrows) return; // reallocate and copy omitting the empty cells QWidget **simplifiedCells = new QWidget*[simplifiedNCols * simplifiedNRows]; std::fill(simplifiedCells, simplifiedCells + simplifiedNCols * simplifiedNRows, nullptr); QWidget **simplifiedPtr = simplifiedCells; for (int r = 0; r < m_nrows; r++) if (rows[r]) for (int c = 0; c < m_ncols; c++) if (columns[c]) { if (QWidget *w = cell(r, c)) *simplifiedPtr = w; simplifiedPtr++; } Q_ASSERT(simplifiedPtr == simplifiedCells + simplifiedNCols * simplifiedNRows); delete [] m_cells; m_cells = simplifiedCells; m_nrows = simplifiedNRows; m_ncols = simplifiedNCols; } bool GridHelper::shrinkFormLayoutSpans() { bool shrunk = false; using WidgetSet = QSet; // Determine unique set of widgets WidgetSet widgets; QWidget **end = m_cells + m_ncols * m_nrows; for (QWidget **wptr = m_cells; wptr < end; wptr++) if (QWidget *w = *wptr) widgets.insert(w); // Restrict the widget span: max horizontal span at column 0: 2, anything else: 1 const int maxRowSpan = 1; for (auto *w : std::as_const(widgets)) { int row, col, rowspan, colspan; if (!locateWidget(w, row, col, rowspan, colspan)) { qDebug("ooops, widget '%s' does not fit in layout", w->objectName().toUtf8().constData()); row = col = rowspan = colspan = 0; } const int maxColSpan = col == 0 ? 2 : 1; const int newColSpan = qMin(colspan, maxColSpan); const int newRowSpan = qMin(rowspan, maxRowSpan); if (newColSpan != colspan || newRowSpan != rowspan) { // in case like this: // W1 W1 // W1 W2 // do: // W1 0 // 0 W2 for (int i = row; i < row + rowspan - 1; i++) for (int j = col; j < col + colspan - 1; j++) if (i > row + newColSpan - 1 || j > col + newRowSpan - 1) if (cell(i, j) == w) setCell(i, j, nullptr); shrunk = true; } } return shrunk; } void GridHelper::reallocFormLayout() { // Columns matching? -> happy! if (m_ncols == FormLayoutColumns) return; // If there are offset columns (starting past the field column), // move them to the left and squeeze them. This also prevents the // following reallocation from creating empty form rows. int pastRightWidgetCount = 0; if (m_ncols > FormLayoutColumns) { for (int r = 0; r < m_nrows; r++) { // Try to find a column where the form columns are empty and // there are widgets further to the right. if (cell(r, 0) == nullptr && cell(r, 1) == nullptr) { int sourceCol = FormLayoutColumns; QWidget *firstWidget = nullptr; for ( ; sourceCol < m_ncols; sourceCol++) if (QWidget *w = cell(r, sourceCol)) { firstWidget = w; break; } if (firstWidget) { // Move/squeeze. Copy to beginning of column if it is a label, else field int targetCol = qobject_cast(firstWidget) ? 0 : 1; for ( ; sourceCol < m_ncols; sourceCol++) if (QWidget *w = cell(r, sourceCol)) setCell(r, targetCol++, w); // Pad with zero for ( ; targetCol < m_ncols; targetCol++) setCell(r, targetCol, nullptr); } } // Any protruding widgets left on that row? for (int c = FormLayoutColumns; c < m_ncols; c++) if (cell(r, c)) pastRightWidgetCount++; } } // Reallocate with 2 columns. Just insert the protruding ones as fields. const int formNRows = m_nrows + pastRightWidgetCount; QWidget **formCells = new QWidget*[FormLayoutColumns * formNRows]; std::fill(formCells, formCells + FormLayoutColumns * formNRows, nullptr); QWidget **formPtr = formCells; const int matchingColumns = qMin(m_ncols, static_cast(FormLayoutColumns)); for (int r = 0; r < m_nrows; r++) { int c = 0; for ( ; c < matchingColumns; c++) // Just copy over matching columns *formPtr++ = cell(r, c); formPtr += FormLayoutColumns - matchingColumns; // In case old format was 1 column // protruding widgets: Insert as single-field rows for ( ; c < m_ncols; c++) if (QWidget *w = cell(r, c)) { formPtr++; *formPtr++ = w; } } Q_ASSERT(formPtr == formCells + FormLayoutColumns * formNRows); delete [] m_cells; m_cells = formCells; m_nrows = formNRows; m_ncols = FormLayoutColumns; } bool GridHelper::locateWidget(QWidget *w, int &row, int &col, int &rowspan, int &colspan) const { const int end = m_nrows * m_ncols; const int startIndex = std::find(m_cells, m_cells + end, w) - m_cells; if (startIndex == end) return false; row = startIndex / m_ncols; col = startIndex % m_ncols; for (rowspan = 1; row + rowspan < m_nrows && cell(row + rowspan, col) == w; rowspan++) {} for (colspan = 1; col + colspan < m_ncols && cell(row, col + colspan) == w; colspan++) {} return true; } // QGridLayout/QFormLayout Helpers: get item position/add item (overloads to make templates work) void addWidgetToGrid(QGridLayout *lt, QWidget * widget, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment) { lt->addWidget(widget, row, column, rowSpan, columnSpan, alignment); } inline void addWidgetToGrid(QFormLayout *lt, QWidget * widget, int row, int column, int, int columnSpan, Qt::Alignment) { formLayoutAddWidget(lt, widget, QRect(column, row, columnSpan, 1), false); } // ----------- Base template for grid like layouts template class GridLayout : public Layout { public: GridLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb); void doLayout() override; void sort() override { setWidgets(buildGrid(widgets())); } protected: QWidgetList buildGrid(const QWidgetList &); GridHelper m_grid; }; template GridLayout::GridLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb) : Layout(wl, p, fw, lb, LayoutInfo::Grid), m_grid(static_cast(GridMode)) { } template void GridLayout::doLayout() { bool needMove, needReparent; if (!prepareLayout(needMove, needReparent)) return; GridLikeLayout *layout = static_cast(createLayout(LayoutType)); if (!m_grid.empty()) sort(); QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. for (auto *w : widgets()) { int r = 0, c = 0, rs = 0, cs = 0; if (m_grid.locateWidget(w, r, c, rs, cs)) { if (needReparent) reparentToLayoutBase(w); Qt::Alignment alignment; if (const Spacer *spacer = qobject_cast(w)) alignment = spacer->alignment(); addWidgetToGrid(layout, w, r, c, rs, cs, alignment); w->show(); } else { qDebug("ooops, widget '%s' does not fit in layout", w->objectName().toUtf8().constData()); } } QLayoutSupport::createEmptyCells(layout); finishLayout(needMove, layout); } // Remove duplicate entries (Remove next, if equal to current) void removeIntVecDuplicates(QList &v) { if (v.size() < 2) return; for (auto current = v.begin() ; (current != v.end()) && ((current + 1) != v.end()) ; ) if ( *current == *(current+1) ) v.erase(current+1); else ++current; } // Ensure a non-zero size for a widget geometry (squeezed spacers) inline QRect expandGeometry(const QRect &rect) { return rect.isEmpty() ? QRect(rect.topLeft(), rect.size().expandedTo(QSize(1, 1))) : rect; } template QWidgetList GridLayout::buildGrid(const QWidgetList &widgetList) { if (widgetList.isEmpty()) return QWidgetList(); // Pixel to cell conversion: // By keeping a list of start'n'stop values (x & y) for each widget, // it is possible to create a very small grid of cells to represent // the widget layout. // ----------------------------------------------------------------- // We need a list of both start and stop values for x- & y-axis const auto widgetCount = widgetList.size(); QList x( widgetCount * 2 ); QList y( widgetCount * 2 ); // Using push_back would look nicer, but operator[] is much faster qsizetype index = 0; for (const auto *w : widgetList) { const QRect widgetPos = expandGeometry(w->geometry()); x[index] = widgetPos.left(); x[index+1] = widgetPos.right(); y[index] = widgetPos.top(); y[index+1] = widgetPos.bottom(); index += 2; } std::sort(x.begin(), x.end()); std::sort(y.begin(), y.end()); // Remove duplicate x entries (Remove next, if equal to current) removeIntVecDuplicates(x); removeIntVecDuplicates(y); // Note that left == right and top == bottom for size 1 items; reserve // enough space m_grid.resize(y.size(), x.size()); for (auto *w : widgetList) { // Mark the cells in the grid that contains a widget const QRect widgetPos = expandGeometry(w->geometry()); QRect c(0, 0, 0, 0); // rect of columns/rows // From left til right (not including) const int leftIdx = x.indexOf(widgetPos.left()); Q_ASSERT(leftIdx != -1); c.setLeft(leftIdx); c.setRight(leftIdx); for (qsizetype cw = leftIdx; cw < x.size(); ++cw) if (x.at(cw) < widgetPos.right()) c.setRight(cw); else break; // From top til bottom (not including) const int topIdx = y.indexOf(widgetPos.top()); Q_ASSERT(topIdx != -1); c.setTop(topIdx); c.setBottom(topIdx); for (qsizetype ch = topIdx; ch < y.size(); ++ch) if (y.at(ch) < widgetPos.bottom()) c.setBottom(ch); else break; m_grid.setCells(c, w); // Mark cellblock } m_grid.simplify(); QWidgetList ordered; for (int i = 0; i < m_grid.numRows(); i++) for (int j = 0; j < m_grid.numCols(); j++) { QWidget *w = m_grid.cell(i, j); if (w && !ordered.contains(w)) ordered.append(w); } return ordered; } } // anonymous Layout* Layout::createLayout(const QWidgetList &widgets, QWidget *parentWidget, QDesignerFormWindowInterface *fw, QWidget *layoutBase, LayoutInfo::Type layoutType) { switch (layoutType) { case LayoutInfo::Grid: return new GridLayout(widgets, parentWidget, fw, layoutBase); case LayoutInfo::HBox: case LayoutInfo::VBox: { const Qt::Orientation orientation = layoutType == LayoutInfo::HBox ? Qt::Horizontal : Qt::Vertical; return new BoxLayout(widgets, parentWidget, fw, layoutBase, orientation); } case LayoutInfo::HSplitter: case LayoutInfo::VSplitter: { const Qt::Orientation orientation = layoutType == LayoutInfo::HSplitter ? Qt::Horizontal : Qt::Vertical; return new SplitterLayout(widgets, parentWidget, fw, layoutBase, orientation); } case LayoutInfo::Form: return new GridLayout(widgets, parentWidget, fw, layoutBase); default: break; } Q_ASSERT(0); return nullptr; } } // namespace qdesigner_internal QT_END_NAMESPACE