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 "qlayout_widget_p.h"
30#include "qdesigner_utils_p.h"
31#include "layout_p.h"
32#include "layoutinfo_p.h"
33#include "invisible_widget_p.h"
34#include "qdesigner_widgetitem_p.h"
35
36#include <QtDesigner/abstractformwindow.h>
37#include <QtDesigner/qextensionmanager.h>
38#include <QtDesigner/abstractformeditor.h>
39#include <QtDesigner/propertysheet.h>
40#include <QtDesigner/abstractwidgetfactory.h>
41
42#include <QtGui/qpainter.h>
43#include <QtWidgets/qboxlayout.h>
44#include <QtWidgets/qgridlayout.h>
45#include <QtWidgets/qformlayout.h>
46#include <QtWidgets/qapplication.h>
47#include <QtGui/qevent.h>
48
49#include <QtCore/qdebug.h>
50#include <QtCore/qalgorithms.h>
51#include <QtCore/qmap.h>
52#include <QtCore/qstack.h>
53#include <QtCore/qpair.h>
54#include <QtCore/qset.h>
55
56#include <algorithm>
57
58enum { ShiftValue = 1 };
59enum { debugLayout = 0 };
60enum { FormLayoutColumns = 2 };
61enum { indicatorSize = 2 };
62// Grid/form Helpers: get info (overloads to make templates work)
63
64namespace { // Do not use static, will break HP-UX due to templates
65
66QT_USE_NAMESPACE
67
68// overloads to make templates over QGridLayout/QFormLayout work
69inline int gridRowCount(const QGridLayout *gridLayout)
70{
71 return gridLayout->rowCount();
72}
73
74inline int gridColumnCount(const QGridLayout *gridLayout)
75{
76 return gridLayout->columnCount();
77}
78
79// QGridLayout/QFormLayout Helpers: get item position (overloads to make templates work)
80inline void getGridItemPosition(QGridLayout *gridLayout, int index,
81 int *row, int *column, int *rowspan, int *colspan)
82{
83 gridLayout->getItemPosition(idx: index, row, column, rowSpan: rowspan, columnSpan: colspan);
84}
85
86QRect gridItemInfo(QGridLayout *grid, int index)
87{
88 int row, column, rowSpan, columnSpan;
89 // getItemPosition is not const, grmbl..
90 grid->getItemPosition(idx: index, row: &row, column: &column, rowSpan: &rowSpan, columnSpan: &columnSpan);
91 return QRect(column, row, columnSpan, rowSpan);
92}
93
94inline int gridRowCount(const QFormLayout *formLayout) { return formLayout->rowCount(); }
95inline int gridColumnCount(const QFormLayout *) { return FormLayoutColumns; }
96
97inline void getGridItemPosition(QFormLayout *formLayout, int index, int *row, int *column, int *rowspan, int *colspan)
98{
99 qdesigner_internal::getFormLayoutItemPosition(formLayout, index, rowPtr: row, columnPtr: column, rowspanPtr: rowspan, colspanPtr: colspan);
100}
101} // namespace anonymous
102
103QT_BEGIN_NAMESPACE
104
105static const char *objectNameC = "objectName";
106static const char *sizeConstraintC = "sizeConstraint";
107
108/* A padding spacer element that is used to represent an empty form layout cell. It should grow with its cell.
109 * Should not be used on a grid as it causes resizing inconsistencies */
110namespace qdesigner_internal {
111 class PaddingSpacerItem : public QSpacerItem {
112 public:
113 PaddingSpacerItem() : QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding) {}
114
115 Qt::Orientations expandingDirections () const override
116 { return Qt::Vertical | Qt::Horizontal; }
117 };
118}
119
120static inline QSpacerItem *createGridSpacer()
121{
122 return new QSpacerItem(0, 0);
123}
124
125static inline QSpacerItem *createFormSpacer()
126{
127 return new qdesigner_internal::PaddingSpacerItem;
128}
129
130// QGridLayout/QFormLayout Helpers: Debug items of GridLikeLayout
131template <class GridLikeLayout>
132static QDebug debugGridLikeLayout(QDebug str, const GridLikeLayout &gl)
133{
134 const int count = gl.count();
135 str << "Grid: " << gl.objectName() << gridRowCount(&gl) << " rows x " << gridColumnCount(&gl)
136 << " cols " << count << " items\n";
137 for (int i = 0; i < count; i++) {
138 QLayoutItem *item = gl.itemAt(i);
139 str << "Item " << i << item << item->widget() << gridItemInfo(const_cast<GridLikeLayout *>(&gl), i) << " empty=" << qdesigner_internal::LayoutInfo::isEmptyItem(item) << "\n";
140 }
141 return str;
142}
143
144static inline QDebug operator<<(QDebug str, const QGridLayout &gl) { return debugGridLikeLayout(str, gl); }
145
146static inline bool isEmptyFormLayoutRow(const QFormLayout *fl, int row)
147{
148 // Spanning can never be empty
149 if (fl->itemAt(row, role: QFormLayout::SpanningRole))
150 return false;
151 return qdesigner_internal::LayoutInfo::isEmptyItem(item: fl->itemAt(row, role: QFormLayout::LabelRole)) && qdesigner_internal::LayoutInfo::isEmptyItem(item: fl->itemAt(row, role: QFormLayout::FieldRole));
152}
153
154static inline bool canSimplifyFormLayout(const QFormLayout *formLayout, const QRect &restrictionArea)
155{
156 if (restrictionArea.x() >= FormLayoutColumns)
157 return false;
158 // Try to find empty rows
159 const int bottomCheckRow = qMin(a: formLayout->rowCount(), b: restrictionArea.top() + restrictionArea.height());
160 for (int r = restrictionArea.y(); r < bottomCheckRow; r++)
161 if (isEmptyFormLayoutRow(fl: formLayout, row: r))
162 return true;
163 return false;
164}
165
166// recreate a managed layout (which does not automagically remove
167// empty rows/columns like grid or form layout) in case it needs to shrink
168
169static QLayout *recreateManagedLayout(const QDesignerFormEditorInterface *core, QWidget *w, QLayout *lt)
170{
171 const qdesigner_internal::LayoutInfo::Type t = qdesigner_internal::LayoutInfo::layoutType(core, layout: lt);
172 qdesigner_internal::LayoutProperties properties;
173 const int mask = properties.fromPropertySheet(core, l: lt, mask: qdesigner_internal::LayoutProperties::AllProperties);
174 qdesigner_internal::LayoutInfo::deleteLayout(core, widget: w);
175 QLayout *rc = core->widgetFactory()->createLayout(widget: w, layout: nullptr, type: t);
176 properties.toPropertySheet(core, l: rc, mask, applyChanged: true);
177 return rc;
178}
179
180// QGridLayout/QFormLayout Helpers: find an item on a form/grid. Return index
181template <class GridLikeLayout>
182int findGridItemAt(GridLikeLayout *gridLayout, int at_row, int at_column)
183{
184 Q_ASSERT(gridLayout);
185 const int count = gridLayout->count();
186 for (int index = 0; index < count; index++) {
187 int row, column, rowspan, colspan;
188 getGridItemPosition(gridLayout, index, &row, &column, &rowspan, &colspan);
189 if (at_row >= row && at_row < (row + rowspan)
190 && at_column >= column && at_column < (column + colspan)) {
191 return index;
192 }
193 }
194 return -1;
195}
196// QGridLayout/QFormLayout Helpers: remove dummy spacers on form/grid
197template <class GridLikeLayout>
198static bool removeEmptyCellsOnGrid(GridLikeLayout *grid, const QRect &area)
199{
200 // check if there are any items in the way. Should be only spacers
201 // Unique out items that span rows/columns.
202 QVector<int> indexesToBeRemoved;
203 indexesToBeRemoved.reserve(asize: grid->count());
204 const int rightColumn = area.x() + area.width();
205 const int bottomRow = area.y() + area.height();
206 for (int c = area.x(); c < rightColumn; c++)
207 for (int r = area.y(); r < bottomRow; r++) {
208 const int index = findGridItemAt(grid, r ,c);
209 if (index != -1)
210 if (QLayoutItem *item = grid->itemAt(index)) {
211 if (qdesigner_internal::LayoutInfo::isEmptyItem(item)) {
212 if (indexesToBeRemoved.indexOf(t: index) == -1)
213 indexesToBeRemoved.push_back(t: index);
214 } else {
215 return false;
216 }
217 }
218 }
219 // remove, starting from last
220 if (!indexesToBeRemoved.isEmpty()) {
221 std::stable_sort(first: indexesToBeRemoved.begin(), last: indexesToBeRemoved.end());
222 for (int i = indexesToBeRemoved.size() - 1; i >= 0; i--)
223 delete grid->takeAt(indexesToBeRemoved[i]);
224 }
225 return true;
226}
227
228namespace qdesigner_internal {
229// --------- LayoutProperties
230
231LayoutProperties::LayoutProperties()
232{
233 clear();
234}
235
236void LayoutProperties::clear()
237{
238 std::fill(first: m_margins, last: m_margins + MarginCount, value: 0);
239 std::fill(first: m_marginsChanged, last: m_marginsChanged + MarginCount, value: false);
240 std::fill(first: m_spacings, last: m_spacings + SpacingsCount, value: 0);
241 std::fill(first: m_spacingsChanged, last: m_spacingsChanged + SpacingsCount, value: false);
242
243 m_objectName = QVariant();
244 m_objectNameChanged = false;
245 m_sizeConstraint = QVariant(QLayout::SetDefaultConstraint);
246 m_sizeConstraintChanged = false;
247
248 m_fieldGrowthPolicyChanged = m_rowWrapPolicyChanged = m_labelAlignmentChanged = m_formAlignmentChanged = false;
249 m_fieldGrowthPolicy = m_rowWrapPolicy = m_formAlignment = QVariant();
250
251 m_boxStretchChanged = m_gridRowStretchChanged = m_gridColumnStretchChanged = m_gridRowMinimumHeightChanged = false;
252 m_boxStretch = m_gridRowStretch = m_gridColumnStretch = m_gridRowMinimumHeight = QVariant();
253}
254
255int LayoutProperties::visibleProperties(const QLayout *layout)
256{
257 // Grid like layout have 2 spacings.
258 const bool isFormLayout = qobject_cast<const QFormLayout*>(object: layout);
259 const bool isGridLike = qobject_cast<const QGridLayout*>(object: layout) || isFormLayout;
260 int rc = ObjectNameProperty|LeftMarginProperty|TopMarginProperty|RightMarginProperty|BottomMarginProperty|
261 SizeConstraintProperty;
262
263 rc |= isGridLike ? (HorizSpacingProperty|VertSpacingProperty) : SpacingProperty;
264 if (isFormLayout) {
265 rc |= FieldGrowthPolicyProperty|RowWrapPolicyProperty|LabelAlignmentProperty|FormAlignmentProperty;
266 } else {
267 if (isGridLike) {
268 rc |= GridRowStretchProperty|GridColumnStretchProperty|GridRowMinimumHeightProperty|GridColumnMinimumWidthProperty;
269 } else {
270 rc |= BoxStretchProperty;
271 }
272 }
273 return rc;
274}
275
276static const char *marginPropertyNamesC[] = {"leftMargin", "topMargin", "rightMargin", "bottomMargin"};
277static const char *spacingPropertyNamesC[] = {"spacing", "horizontalSpacing", "verticalSpacing" };
278static const char *fieldGrowthPolicyPropertyC = "fieldGrowthPolicy";
279static const char *rowWrapPolicyPropertyC = "rowWrapPolicy";
280static const char *labelAlignmentPropertyC = "labelAlignment";
281static const char *formAlignmentPropertyC = "formAlignment";
282static const char *boxStretchPropertyC = "stretch";
283static const char *gridRowStretchPropertyC = "rowStretch";
284static const char *gridColumnStretchPropertyC = "columnStretch";
285static const char *gridRowMinimumHeightPropertyC = "rowMinimumHeight";
286static const char *gridColumnMinimumWidthPropertyC = "columnMinimumWidth";
287
288static bool intValueFromSheet(const QDesignerPropertySheetExtension *sheet, const QString &name, int *value, bool *changed)
289{
290 const int sheetIndex = sheet->indexOf(name);
291 if (sheetIndex == -1)
292 return false;
293 *value = sheet->property(index: sheetIndex).toInt();
294 *changed = sheet->isChanged(index: sheetIndex);
295 return true;
296}
297
298static void variantPropertyFromSheet(int mask, int flag, const QDesignerPropertySheetExtension *sheet, const QString &name,
299 QVariant *value, bool *changed, int *returnMask)
300{
301 if (mask & flag) {
302 const int sIndex = sheet->indexOf(name);
303 if (sIndex != -1) {
304 *value = sheet->property(index: sIndex);
305 *changed = sheet->isChanged(index: sIndex);
306 *returnMask |= flag;
307 }
308 }
309}
310
311int LayoutProperties::fromPropertySheet(const QDesignerFormEditorInterface *core, QLayout *l, int mask)
312{
313 int rc = 0;
314 const QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: core->extensionManager(), object: l);
315 Q_ASSERT(sheet);
316 // name
317 if (mask & ObjectNameProperty) {
318 const int nameIndex = sheet->indexOf(name: QLatin1String(objectNameC));
319 Q_ASSERT(nameIndex != -1);
320 m_objectName = sheet->property(index: nameIndex);
321 m_objectNameChanged = sheet->isChanged(index: nameIndex);
322 rc |= ObjectNameProperty;
323 }
324 // -- Margins
325 const int marginFlags[MarginCount] = { LeftMarginProperty, TopMarginProperty, RightMarginProperty, BottomMarginProperty};
326 for (int i = 0; i < MarginCount; i++)
327 if (mask & marginFlags[i])
328 if (intValueFromSheet(sheet, name: QLatin1String(marginPropertyNamesC[i]), value: m_margins + i, changed: m_marginsChanged + i))
329 rc |= marginFlags[i];
330
331 const int spacingFlags[] = { SpacingProperty, HorizSpacingProperty, VertSpacingProperty};
332 for (int i = 0; i < SpacingsCount; i++)
333 if (mask & spacingFlags[i])
334 if (intValueFromSheet(sheet, name: QLatin1String(spacingPropertyNamesC[i]), value: m_spacings + i, changed: m_spacingsChanged + i))
335 rc |= spacingFlags[i];
336 // sizeConstraint, flags
337 variantPropertyFromSheet(mask, flag: SizeConstraintProperty, sheet, name: QLatin1String(sizeConstraintC), value: &m_sizeConstraint, changed: &m_sizeConstraintChanged, returnMask: &rc);
338 variantPropertyFromSheet(mask, flag: FieldGrowthPolicyProperty, sheet, name: QLatin1String(fieldGrowthPolicyPropertyC), value: &m_fieldGrowthPolicy, changed: &m_fieldGrowthPolicyChanged, returnMask: &rc);
339 variantPropertyFromSheet(mask, flag: RowWrapPolicyProperty, sheet, name: QLatin1String(rowWrapPolicyPropertyC), value: &m_rowWrapPolicy, changed: &m_rowWrapPolicyChanged, returnMask: &rc);
340 variantPropertyFromSheet(mask, flag: LabelAlignmentProperty, sheet, name: QLatin1String(labelAlignmentPropertyC), value: &m_labelAlignment, changed: &m_labelAlignmentChanged, returnMask: &rc);
341 variantPropertyFromSheet(mask, flag: FormAlignmentProperty, sheet, name: QLatin1String(formAlignmentPropertyC), value: &m_formAlignment, changed: &m_formAlignmentChanged, returnMask: &rc);
342 variantPropertyFromSheet(mask, flag: BoxStretchProperty, sheet, name: QLatin1String(boxStretchPropertyC), value: &m_boxStretch, changed: & m_boxStretchChanged, returnMask: &rc);
343 variantPropertyFromSheet(mask, flag: GridRowStretchProperty, sheet, name: QLatin1String(gridRowStretchPropertyC), value: &m_gridRowStretch, changed: &m_gridRowStretchChanged, returnMask: &rc);
344 variantPropertyFromSheet(mask, flag: GridColumnStretchProperty, sheet, name: QLatin1String(gridColumnStretchPropertyC), value: &m_gridColumnStretch, changed: &m_gridColumnStretchChanged, returnMask: &rc);
345 variantPropertyFromSheet(mask, flag: GridRowMinimumHeightProperty, sheet, name: QLatin1String(gridRowMinimumHeightPropertyC), value: &m_gridRowMinimumHeight, changed: &m_gridRowMinimumHeightChanged, returnMask: &rc);
346 variantPropertyFromSheet(mask, flag: GridColumnMinimumWidthProperty, sheet, name: QLatin1String(gridColumnMinimumWidthPropertyC), value: &m_gridColumnMinimumWidth, changed: &m_gridColumnMinimumWidthChanged, returnMask: &rc);
347 return rc;
348}
349
350static bool intValueToSheet(QDesignerPropertySheetExtension *sheet, const QString &name, int value, bool changed, bool applyChanged)
351
352{
353
354 const int sheetIndex = sheet->indexOf(name);
355 if (sheetIndex == -1) {
356 qWarning() << " LayoutProperties: Attempt to set property " << name << " that does not exist for the layout.";
357 return false;
358 }
359 sheet->setProperty(index: sheetIndex, value: QVariant(value));
360 if (applyChanged)
361 sheet->setChanged(index: sheetIndex, changed);
362 return true;
363}
364
365static void variantPropertyToSheet(int mask, int flag, bool applyChanged, QDesignerPropertySheetExtension *sheet, const QString &name,
366 const QVariant &value, bool changed, int *returnMask)
367{
368 if (mask & flag) {
369 const int sIndex = sheet->indexOf(name);
370 if (sIndex != -1) {
371 sheet->setProperty(index: sIndex, value);
372 if (applyChanged)
373 sheet->setChanged(index: sIndex, changed);
374 *returnMask |= flag;
375 }
376 }
377}
378
379int LayoutProperties::toPropertySheet(const QDesignerFormEditorInterface *core, QLayout *l, int mask, bool applyChanged) const
380{
381 int rc = 0;
382 QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(manager: core->extensionManager(), object: l);
383 Q_ASSERT(sheet);
384 // name
385 if (mask & ObjectNameProperty) {
386 const int nameIndex = sheet->indexOf(name: QLatin1String(objectNameC));
387 Q_ASSERT(nameIndex != -1);
388 sheet->setProperty(index: nameIndex, value: m_objectName);
389 if (applyChanged)
390 sheet->setChanged(index: nameIndex, changed: m_objectNameChanged);
391 rc |= ObjectNameProperty;
392 }
393 // margins
394 const int marginFlags[MarginCount] = { LeftMarginProperty, TopMarginProperty, RightMarginProperty, BottomMarginProperty};
395 for (int i = 0; i < MarginCount; i++)
396 if (mask & marginFlags[i])
397 if (intValueToSheet(sheet, name: QLatin1String(marginPropertyNamesC[i]), value: m_margins[i], changed: m_marginsChanged[i], applyChanged))
398 rc |= marginFlags[i];
399
400 const int spacingFlags[] = { SpacingProperty, HorizSpacingProperty, VertSpacingProperty};
401 for (int i = 0; i < SpacingsCount; i++)
402 if (mask & spacingFlags[i])
403 if (intValueToSheet(sheet, name: QLatin1String(spacingPropertyNamesC[i]), value: m_spacings[i], changed: m_spacingsChanged[i], applyChanged))
404 rc |= spacingFlags[i];
405 // sizeConstraint
406 variantPropertyToSheet(mask, flag: SizeConstraintProperty, applyChanged, sheet, name: QLatin1String(sizeConstraintC), value: m_sizeConstraint, changed: m_sizeConstraintChanged, returnMask: &rc);
407 variantPropertyToSheet(mask, flag: FieldGrowthPolicyProperty, applyChanged, sheet, name: QLatin1String(fieldGrowthPolicyPropertyC), value: m_fieldGrowthPolicy, changed: m_fieldGrowthPolicyChanged, returnMask: &rc);
408 variantPropertyToSheet(mask, flag: RowWrapPolicyProperty, applyChanged, sheet, name: QLatin1String(rowWrapPolicyPropertyC), value: m_rowWrapPolicy, changed: m_rowWrapPolicyChanged, returnMask: &rc);
409 variantPropertyToSheet(mask, flag: LabelAlignmentProperty, applyChanged, sheet, name: QLatin1String(labelAlignmentPropertyC), value: m_labelAlignment, changed: m_labelAlignmentChanged, returnMask: &rc);
410 variantPropertyToSheet(mask, flag: FormAlignmentProperty, applyChanged, sheet, name: QLatin1String(formAlignmentPropertyC), value: m_formAlignment, changed: m_formAlignmentChanged, returnMask: &rc);
411 variantPropertyToSheet(mask, flag: BoxStretchProperty, applyChanged, sheet, name: QLatin1String(boxStretchPropertyC), value: m_boxStretch, changed: m_boxStretchChanged, returnMask: &rc);
412 variantPropertyToSheet(mask, flag: GridRowStretchProperty, applyChanged, sheet, name: QLatin1String(gridRowStretchPropertyC), value: m_gridRowStretch, changed: m_gridRowStretchChanged, returnMask: &rc);
413 variantPropertyToSheet(mask, flag: GridColumnStretchProperty, applyChanged, sheet, name: QLatin1String(gridColumnStretchPropertyC), value: m_gridColumnStretch, changed: m_gridColumnStretchChanged, returnMask: &rc);
414 variantPropertyToSheet(mask, flag: GridRowMinimumHeightProperty, applyChanged, sheet, name: QLatin1String(gridRowMinimumHeightPropertyC), value: m_gridRowMinimumHeight, changed: m_gridRowMinimumHeightChanged, returnMask: &rc);
415 variantPropertyToSheet(mask, flag: GridColumnMinimumWidthProperty, applyChanged, sheet, name: QLatin1String(gridColumnMinimumWidthPropertyC), value: m_gridColumnMinimumWidth, changed: m_gridColumnMinimumWidthChanged, returnMask: &rc);
416 return rc;
417}
418
419// ---------------- LayoutHelper
420LayoutHelper::LayoutHelper() = default;
421
422LayoutHelper::~LayoutHelper() = default;
423
424int LayoutHelper::indexOf(const QLayout *lt, const QWidget *widget)
425{
426 if (!lt)
427 return -1;
428
429 const int itemCount = lt->count();
430 for (int i = 0; i < itemCount; i++)
431 if (lt->itemAt(index: i)->widget() == widget)
432 return i;
433 return -1;
434}
435
436QRect LayoutHelper::itemInfo(QLayout *lt, const QWidget *widget) const
437{
438 const int index = indexOf(lt, widget);
439 if (index == -1) {
440 qWarning() << "LayoutHelper::itemInfo: " << widget << " not in layout " << lt;
441 return QRect(0, 0, 1, 1);
442 }
443 return itemInfo(lt, index);
444}
445
446 // ---------------- BoxLayoutHelper
447 class BoxLayoutHelper : public LayoutHelper {
448 public:
449 BoxLayoutHelper(const Qt::Orientation orientation) : m_orientation(orientation) {}
450
451 QRect itemInfo(QLayout *lt, int index) const override;
452 void insertWidget(QLayout *lt, const QRect &info, QWidget *w) override;
453 void removeWidget(QLayout *lt, QWidget *widget) override;
454 void replaceWidget(QLayout *lt, QWidget *before, QWidget *after) override;
455
456 void pushState(const QDesignerFormEditorInterface *, const QWidget *) override;
457 void popState(const QDesignerFormEditorInterface *, QWidget *) override;
458
459 bool canSimplify(const QDesignerFormEditorInterface *, const QWidget *, const QRect &) const override { return false; }
460 void simplify(const QDesignerFormEditorInterface *, QWidget *, const QRect &) override {}
461
462 // Helper for restoring layout states
463 using LayoutItemVector = QVector<QLayoutItem *>;
464 static LayoutItemVector disassembleLayout(QLayout *lt);
465 static QLayoutItem *findItemOfWidget(const LayoutItemVector &lv, QWidget *w);
466
467 private:
468 using BoxLayoutState = QVector<QWidget *>;
469
470 static BoxLayoutState state(const QBoxLayout*lt);
471
472 QStack<BoxLayoutState> m_states;
473 const Qt::Orientation m_orientation;
474 };
475
476 QRect BoxLayoutHelper::itemInfo(QLayout * /*lt*/, int index) const
477 {
478 return m_orientation == Qt::Horizontal ? QRect(index, 0, 1, 1) : QRect(0, index, 1, 1);
479 }
480
481 void BoxLayoutHelper::insertWidget(QLayout *lt, const QRect &info, QWidget *w)
482 {
483 QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem.
484 QBoxLayout *boxLayout = qobject_cast<QBoxLayout *>(object: lt);
485 Q_ASSERT(boxLayout);
486 boxLayout->insertWidget(index: m_orientation == Qt::Horizontal ? info.x() : info.y(), widget: w);
487 }
488
489 void BoxLayoutHelper::removeWidget(QLayout *lt, QWidget *widget)
490 {
491 QBoxLayout *boxLayout = qobject_cast<QBoxLayout *>(object: lt);
492 Q_ASSERT(boxLayout);
493 boxLayout->removeWidget(w: widget);
494 }
495
496 void BoxLayoutHelper::replaceWidget(QLayout *lt, QWidget *before, QWidget *after)
497 {
498 bool ok = false;
499 QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem.
500 if (QBoxLayout *boxLayout = qobject_cast<QBoxLayout *>(object: lt)) {
501 const int index = boxLayout->indexOf(before);
502 if (index != -1) {
503 const bool visible = before->isVisible();
504 delete boxLayout->takeAt(index);
505 if (visible)
506 before->hide();
507 before->setParent(nullptr);
508 boxLayout->insertWidget(index, widget: after);
509 ok = true;
510 }
511 }
512 if (!ok)
513 qWarning() << "BoxLayoutHelper::replaceWidget : Unable to replace " << before << " by " << after << " in " << lt;
514 }
515
516 BoxLayoutHelper::BoxLayoutState BoxLayoutHelper::state(const QBoxLayout*lt)
517 {
518 BoxLayoutState rc;
519 if (const int count = lt->count()) {
520 rc.reserve(asize: count);
521 for (int i = 0; i < count; i++)
522 if (QWidget *w = lt->itemAt(i)->widget())
523 rc.push_back(t: w);
524 }
525 return rc;
526 }
527
528 void BoxLayoutHelper::pushState(const QDesignerFormEditorInterface *core, const QWidget *w)
529 {
530 const QBoxLayout *boxLayout = qobject_cast<const QBoxLayout *>(object: LayoutInfo::managedLayout(core, widget: w));
531 Q_ASSERT(boxLayout);
532 m_states.push(t: state(lt: boxLayout));
533 }
534
535 QLayoutItem *BoxLayoutHelper::findItemOfWidget(const LayoutItemVector &lv, QWidget *w)
536 {
537 const LayoutItemVector::const_iterator cend = lv.constEnd();
538 for (LayoutItemVector::const_iterator it = lv.constBegin(); it != cend; ++it)
539 if ( (*it)->widget() == w)
540 return *it;
541
542 return nullptr;
543 }
544
545 BoxLayoutHelper::LayoutItemVector BoxLayoutHelper::disassembleLayout(QLayout *lt)
546 {
547 // Take items
548 const int count = lt->count();
549 if (count == 0)
550 return LayoutItemVector();
551 LayoutItemVector rc;
552 rc.reserve(asize: count);
553 for (int i = count - 1; i >= 0; i--)
554 rc.push_back(t: lt->takeAt(index: i));
555 return rc;
556 }
557
558 void BoxLayoutHelper::popState(const QDesignerFormEditorInterface *core, QWidget *w)
559 {
560 QBoxLayout *boxLayout = qobject_cast<QBoxLayout *>(object: LayoutInfo::managedLayout(core, widget: w));
561 Q_ASSERT(boxLayout);
562 const BoxLayoutState savedState = m_states.pop();
563 const BoxLayoutState currentState = state(lt: boxLayout);
564 // Check for equality/empty. Note that this will currently
565 // always trigger as box layouts do not have a state apart from
566 // the order and there is no layout order editor yet.
567 if (savedState == state(lt: boxLayout))
568 return;
569
570 const int count = savedState.size();
571 Q_ASSERT(count == currentState.size());
572 // Take items and reassemble in saved order
573 const LayoutItemVector items = disassembleLayout(lt: boxLayout);
574 for (int i = 0; i < count; i++) {
575 QLayoutItem *item = findItemOfWidget(lv: items, w: savedState[i]);
576 Q_ASSERT(item);
577 boxLayout->addItem(item);
578 }
579 }
580
581 // Grid Layout state. Datatype storing the state of a GridLayout as a map of
582 // widgets to QRect(columns, rows) and size. Used to store the state for undo operations
583 // that do not change the widgets within the layout; also provides some manipulation
584 // functions and ability to apply the state to a layout provided its widgets haven't changed.
585 struct GridLayoutState {
586 GridLayoutState() = default;
587
588 void fromLayout(QGridLayout *l);
589 void applyToLayout(const QDesignerFormEditorInterface *core, QWidget *w) const;
590
591 void insertRow(int row);
592 void insertColumn(int column);
593
594 bool simplify(const QRect &r, bool testOnly);
595 void removeFreeRow(int row);
596 void removeFreeColumn(int column);
597
598
599 // State of a cell in one dimension
600 enum DimensionCellState {
601 Free,
602 Spanned, // Item spans it
603 Occupied // Item bordering on it
604 };
605 // Horiontal, Vertical pair of state
606 typedef QPair<DimensionCellState, DimensionCellState> CellState;
607 using CellStates = QVector<CellState>;
608
609 // Figure out states of a cell and return as a flat vector of
610 // [column1, column2,...] (address as row * columnCount + col)
611 static CellStates cellStates(const QList<QRect> &rects, int numRows, int numColumns);
612
613 typedef QMap<QWidget *, QRect> WidgetItemMap;
614 typedef QMap<QWidget *, Qt::Alignment> WidgetAlignmentMap;
615
616 WidgetItemMap widgetItemMap;
617 WidgetAlignmentMap widgetAlignmentMap;
618
619 int rowCount = 0;
620 int colCount = 0;
621 };
622
623 static inline bool needsSpacerItem(const GridLayoutState::CellState &cs) {
624 return cs.first == GridLayoutState::Free && cs.second == GridLayoutState::Free;
625 }
626
627 static inline QDebug operator<<(QDebug str, const GridLayoutState &gs)
628 {
629 str << "GridLayoutState: " << gs.rowCount << " rows x " << gs.colCount
630 << " cols " << gs.widgetItemMap.size() << " items\n";
631
632 const GridLayoutState::WidgetItemMap::const_iterator wcend = gs.widgetItemMap.constEnd();
633 for (GridLayoutState::WidgetItemMap::const_iterator it = gs.widgetItemMap.constBegin(); it != wcend; ++it)
634 str << "Item " << it.key() << it.value() << '\n';
635 return str;
636 }
637
638 GridLayoutState::CellStates GridLayoutState::cellStates(const QList<QRect> &rects, int numRows, int numColumns)
639 {
640 CellStates rc = CellStates(numRows * numColumns, CellState(Free, Free));
641 for (const auto &rect : rects) {
642 const int leftColumn = rect.x();
643 const int topRow = rect.y();
644 const int rightColumn = leftColumn + rect.width() - 1;
645 const int bottomRow = topRow + rect.height() - 1;
646 for (int r = topRow; r <= bottomRow; r++)
647 for (int c = leftColumn; c <= rightColumn; c++) {
648 const int flatIndex = r * numColumns + c;
649 // Bordering horizontally?
650 DimensionCellState &horizState = rc[flatIndex].first;
651 if (c == leftColumn || c == rightColumn) {
652 horizState = Occupied;
653 } else {
654 if (horizState < Spanned)
655 horizState = Spanned;
656 }
657 // Bordering vertically?
658 DimensionCellState &vertState = rc[flatIndex].second;
659 if (r == topRow || r == bottomRow) {
660 vertState = Occupied;
661 } else {
662 if (vertState < Spanned)
663 vertState = Spanned;
664 }
665 }
666 }
667 if (debugLayout) {
668 qDebug() << "GridLayoutState::cellStates: " << numRows << " x " << numColumns;
669 for (int r = 0; r < numRows; r++)
670 for (int c = 0; c < numColumns; c++)
671 qDebug() << " Row: " << r << " column: " << c << rc[r * numColumns + c];
672 }
673 return rc;
674 }
675
676 void GridLayoutState::fromLayout(QGridLayout *l)
677 {
678 rowCount = l->rowCount();
679 colCount = l->columnCount();
680 const int count = l->count();
681 for (int i = 0; i < count; i++) {
682 QLayoutItem *item = l->itemAt(index: i);
683 if (!LayoutInfo::isEmptyItem(item)) {
684 widgetItemMap.insert(akey: item->widget(), avalue: gridItemInfo(grid: l, index: i));
685 if (item->alignment())
686 widgetAlignmentMap.insert(akey: item->widget(), avalue: item->alignment());
687 }
688 }
689 }
690
691 void GridLayoutState::applyToLayout(const QDesignerFormEditorInterface *core, QWidget *w) const
692 {
693 using LayoutItemRectMap =QHash<QLayoutItem *, QRect>;
694 QGridLayout *grid = qobject_cast<QGridLayout *>(object: LayoutInfo::managedLayout(core, widget: w));
695 Q_ASSERT(grid);
696 if (debugLayout)
697 qDebug() << ">GridLayoutState::applyToLayout" << *this << *grid;
698 const bool shrink = grid->rowCount() > rowCount || grid->columnCount() > colCount;
699 // Build a map of existing items to rectangles via widget map, delete spacers
700 LayoutItemRectMap itemMap;
701 while (grid->count()) {
702 QLayoutItem *item = grid->takeAt(index: 0);
703 if (!LayoutInfo::isEmptyItem(item)) {
704 QWidget *itemWidget = item->widget();
705 const WidgetItemMap::const_iterator it = widgetItemMap.constFind(akey: itemWidget);
706 if (it == widgetItemMap.constEnd())
707 qFatal(msg: "GridLayoutState::applyToLayout: Attempt to apply to a layout that has a widget '%s'/'%s' added after saving the state.",
708 itemWidget->metaObject()->className(), itemWidget->objectName().toUtf8().constData());
709 itemMap.insert(akey: item, avalue: it.value());
710 } else {
711 delete item;
712 }
713 }
714 Q_ASSERT(itemMap.size() == widgetItemMap.size());
715 // recreate if shrink
716 if (shrink)
717 grid = static_cast<QGridLayout*>(recreateManagedLayout(core, w, lt: grid));
718
719 // Add widgets items
720 const LayoutItemRectMap::const_iterator icend = itemMap.constEnd();
721 for (LayoutItemRectMap::const_iterator it = itemMap.constBegin(); it != icend; ++it) {
722 const QRect info = it.value();
723 const Qt::Alignment alignment = widgetAlignmentMap.value(akey: it.key()->widget(), adefaultValue: {});
724 grid->addItem(item: it.key(), row: info.y(), column: info.x(), rowSpan: info.height(), columnSpan: info.width(), alignment);
725 }
726 // create spacers
727 const CellStates cs = cellStates(rects: itemMap.values(), numRows: rowCount, numColumns: colCount);
728 for (int r = 0; r < rowCount; r++)
729 for (int c = 0; c < colCount; c++)
730 if (needsSpacerItem(cs: cs[r * colCount + c]))
731 grid->addItem(item: createGridSpacer(), row: r, column: c);
732 grid->activate();
733 if (debugLayout)
734 qDebug() << "<GridLayoutState::applyToLayout" << *grid;
735 }
736
737 void GridLayoutState::insertRow(int row)
738 {
739 rowCount++;
740 for (auto it = widgetItemMap.begin(), iend = widgetItemMap.end(); it != iend; ++it) {
741 const int topRow = it.value().y();
742 if (topRow >= row) {
743 it.value().translate(dx: 0, dy: 1);
744 } else { //Over it: Does it span it -> widen?
745 const int rowSpan = it.value().height();
746 if (rowSpan > 1 && topRow + rowSpan > row)
747 it.value().setHeight(rowSpan + 1);
748 }
749 }
750 }
751
752 void GridLayoutState::insertColumn(int column)
753 {
754 colCount++;
755 for (auto it = widgetItemMap.begin(), iend = widgetItemMap.end(); it != iend; ++it) {
756 const int leftColumn = it.value().x();
757 if (leftColumn >= column) {
758 it.value().translate(dx: 1, dy: 0);
759 } else { // Left of it: Does it span it -> widen?
760 const int colSpan = it.value().width();
761 if (colSpan > 1 && leftColumn + colSpan > column)
762 it.value().setWidth(colSpan + 1);
763 }
764 }
765 }
766
767 // Simplify: Remove empty columns/rows and such ones that are only spanned (shrink
768 // spanning items).
769 // 'AB.C.' 'ABC'
770 // 'DDDD.' ==> 'DDD'
771 // 'EF.G.' 'EFG'
772 bool GridLayoutState::simplify(const QRect &r, bool testOnly)
773 {
774 // figure out free rows/columns.
775 QVector<bool> occupiedRows(rowCount, false);
776 QVector<bool> occupiedColumns(colCount, false);
777 // Mark everything outside restriction rectangle as occupied
778 const int restrictionLeftColumn = r.x();
779 const int restrictionRightColumn = restrictionLeftColumn + r.width();
780 const int restrictionTopRow = r.y();
781 const int restrictionBottomRow = restrictionTopRow + r.height();
782 if (restrictionLeftColumn > 0 || restrictionRightColumn < colCount ||
783 restrictionTopRow > 0 || restrictionBottomRow < rowCount) {
784 for (int r = 0; r < rowCount; r++)
785 if (r < restrictionTopRow || r >= restrictionBottomRow)
786 occupiedRows[r] = true;
787 for (int c = 0; c < colCount; c++)
788 if (c < restrictionLeftColumn || c >= restrictionRightColumn)
789 occupiedColumns[c] = true;
790 }
791 // figure out free fields and tick off occupied rows and columns
792 const CellStates cs = cellStates(rects: widgetItemMap.values(), numRows: rowCount, numColumns: colCount);
793 for (int r = 0; r < rowCount; r++)
794 for (int c = 0; c < colCount; c++) {
795 const CellState &state = cs[r * colCount + c];
796 if (state.first == Occupied)
797 occupiedColumns[c] = true;
798 if (state.second == Occupied)
799 occupiedRows[r] = true;
800 }
801 // Any free rows/columns?
802 if (occupiedRows.indexOf(t: false) == -1 && occupiedColumns.indexOf(t: false) == -1)
803 return false;
804 if (testOnly)
805 return true;
806 // remove rows
807 for (int r = rowCount - 1; r >= 0; r--)
808 if (!occupiedRows[r])
809 removeFreeRow(row: r);
810 // remove columns
811 for (int c = colCount - 1; c >= 0; c--)
812 if (!occupiedColumns[c])
813 removeFreeColumn(column: c);
814 return true;
815 }
816
817 void GridLayoutState::removeFreeRow(int removeRow)
818 {
819 for (auto it = widgetItemMap.begin(), iend = widgetItemMap.end(); it != iend; ++it) {
820 const int r = it.value().y();
821 Q_ASSERT(r != removeRow); // Free rows only
822 if (r < removeRow) { // Does the item span it? - shrink it
823 const int rowSpan = it.value().height();
824 if (rowSpan > 1) {
825 const int bottomRow = r + rowSpan;
826 if (bottomRow > removeRow)
827 it.value().setHeight(rowSpan - 1);
828 }
829 } else
830 if (r > removeRow) // Item below it? - move.
831 it.value().translate(dx: 0, dy: -1);
832 }
833 rowCount--;
834 }
835
836 void GridLayoutState::removeFreeColumn(int removeColumn)
837 {
838 for (auto it = widgetItemMap.begin(), iend = widgetItemMap.end(); it != iend; ++it) {
839 const int c = it.value().x();
840 Q_ASSERT(c != removeColumn); // Free columns only
841 if (c < removeColumn) { // Does the item span it? - shrink it
842 const int colSpan = it.value().width();
843 if (colSpan > 1) {
844 const int rightColumn = c + colSpan;
845 if (rightColumn > removeColumn)
846 it.value().setWidth(colSpan - 1);
847 }
848 } else
849 if (c > removeColumn) // Item to the right of it? - move.
850 it.value().translate(dx: -1, dy: 0);
851 }
852 colCount--;
853 }
854
855 // ---------------- GridLayoutHelper
856 class GridLayoutHelper : public LayoutHelper {
857 public:
858 GridLayoutHelper() = default;
859
860 QRect itemInfo(QLayout *lt, int index) const override;
861 void insertWidget(QLayout *lt, const QRect &info, QWidget *w) override;
862 void removeWidget(QLayout *lt, QWidget *widget) override;
863 void replaceWidget(QLayout *lt, QWidget *before, QWidget *after) override;
864
865 void pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout) override;
866 void popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout) override;
867
868 bool canSimplify(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout, const QRect &restrictionArea) const override;
869 void simplify(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout, const QRect &restrictionArea) override;
870
871 static void insertRow(QGridLayout *grid, int row);
872
873 private:
874 QStack<GridLayoutState> m_states;
875 };
876
877 void GridLayoutHelper::insertRow(QGridLayout *grid, int row)
878 {
879 GridLayoutState state;
880 state.fromLayout(l: grid);
881 state.insertRow(row);
882 QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(obj: grid);
883 state.applyToLayout(core: fw->core(), w: grid->parentWidget());
884 }
885
886 QRect GridLayoutHelper::itemInfo(QLayout * lt, int index) const
887 {
888 QGridLayout *grid = qobject_cast<QGridLayout *>(object: lt);
889 Q_ASSERT(grid);
890 return gridItemInfo(grid, index);
891 }
892
893 void GridLayoutHelper::insertWidget(QLayout *lt, const QRect &info, QWidget *w)
894 {
895 QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem.
896 QGridLayout *gridLayout = qobject_cast<QGridLayout *>(object: lt);
897 Q_ASSERT(gridLayout);
898 // check if there are any items. Should be only spacers, else something is wrong
899 const int row = info.y();
900 int column = info.x();
901 int colSpan = info.width();
902 int rowSpan = info.height();
903 // If not empty: A multiselection was dropped on an empty item, insert row
904 // and spread items along new row
905 if (!removeEmptyCellsOnGrid(grid: gridLayout, area: info)) {
906 int freeColumn = -1;
907 colSpan = rowSpan = 1;
908 // First look to the right for a free column
909 const int columnCount = gridLayout->columnCount();
910 for (int c = column; c < columnCount; c++) {
911 const int idx = findGridItemAt(gridLayout, at_row: row, at_column: c);
912 if (idx != -1 && LayoutInfo::isEmptyItem(item: gridLayout->itemAt(index: idx))) {
913 freeColumn = c;
914 break;
915 }
916 }
917 if (freeColumn != -1) {
918 removeEmptyCellsOnGrid(grid: gridLayout, area: QRect(freeColumn, row, 1, 1));
919 column = freeColumn;
920 } else {
921 GridLayoutHelper::insertRow(grid: gridLayout, row);
922 column = 0;
923 }
924 }
925 gridLayout->addWidget(w, row , column, rowSpan, columnSpan: colSpan);
926 }
927
928 void GridLayoutHelper::removeWidget(QLayout *lt, QWidget *widget)
929 {
930 QGridLayout *gridLayout = qobject_cast<QGridLayout *>(object: lt);
931 Q_ASSERT(gridLayout);
932 const int index = gridLayout->indexOf(widget);
933 if (index == -1) {
934 qWarning() << "GridLayoutHelper::removeWidget : Attempt to remove " << widget << " which is not in the layout.";
935 return;
936 }
937 // delete old item and pad with by spacer items
938 int row, column, rowspan, colspan;
939 gridLayout->getItemPosition(idx: index, row: &row, column: &column, rowSpan: &rowspan, columnSpan: &colspan);
940 delete gridLayout->takeAt(index);
941 const int rightColumn = column + colspan;
942 const int bottomRow = row + rowspan;
943 for (int c = column; c < rightColumn; c++)
944 for (int r = row; r < bottomRow; r++)
945 gridLayout->addItem(item: createGridSpacer(), row: r, column: c);
946 }
947
948 void GridLayoutHelper::replaceWidget(QLayout *lt, QWidget *before, QWidget *after)
949 {
950 bool ok = false;
951 QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem.
952 if (QGridLayout *gridLayout = qobject_cast<QGridLayout *>(object: lt)) {
953 const int index = gridLayout->indexOf(before);
954 if (index != -1) {
955 int row, column, rowSpan, columnSpan;
956 gridLayout->getItemPosition (idx: index, row: &row, column: &column, rowSpan: &rowSpan, columnSpan: &columnSpan);
957 const bool visible = before->isVisible();
958 delete gridLayout->takeAt(index);
959 if (visible)
960 before->hide();
961 before->setParent(nullptr);
962 gridLayout->addWidget(after, row, column, rowSpan, columnSpan);
963 ok = true;
964 }
965 }
966 if (!ok)
967 qWarning() << "GridLayoutHelper::replaceWidget : Unable to replace " << before << " by " << after << " in " << lt;
968 }
969
970 void GridLayoutHelper::pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout)
971 {
972 QGridLayout *gridLayout = qobject_cast<QGridLayout *>(object: LayoutInfo::managedLayout(core, widget: widgetWithManagedLayout));
973 Q_ASSERT(gridLayout);
974 GridLayoutState gs;
975 gs.fromLayout(l: gridLayout);
976 m_states.push(t: gs);
977 }
978
979 void GridLayoutHelper::popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout)
980 {
981 Q_ASSERT(!m_states.isEmpty());
982 const GridLayoutState state = m_states.pop();
983 state.applyToLayout(core, w: widgetWithManagedLayout);
984 }
985
986 bool GridLayoutHelper::canSimplify(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout, const QRect &restrictionArea) const
987 {
988 QGridLayout *gridLayout = qobject_cast<QGridLayout *>(object: LayoutInfo::managedLayout(core, widget: widgetWithManagedLayout));
989 Q_ASSERT(gridLayout);
990 GridLayoutState gs;
991 gs.fromLayout(l: gridLayout);
992 return gs.simplify(r: restrictionArea, testOnly: true);
993 }
994
995 void GridLayoutHelper::simplify(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout, const QRect &restrictionArea)
996 {
997 QGridLayout *gridLayout = qobject_cast<QGridLayout *>(object: LayoutInfo::managedLayout(core, widget: widgetWithManagedLayout));
998 Q_ASSERT(gridLayout);
999 if (debugLayout)
1000 qDebug() << ">GridLayoutHelper::simplify" << *gridLayout;
1001 GridLayoutState gs;
1002 gs.fromLayout(l: gridLayout);
1003 if (gs.simplify(r: restrictionArea, testOnly: false))
1004 gs.applyToLayout(core, w: widgetWithManagedLayout);
1005 if (debugLayout)
1006 qDebug() << "<GridLayoutHelper::simplify" << *gridLayout;
1007 }
1008
1009 // ---------------- FormLayoutHelper
1010 class FormLayoutHelper : public LayoutHelper {
1011 public:
1012 typedef QPair<QWidget *, QWidget *> WidgetPair;
1013 using FormLayoutState = QVector<WidgetPair>;
1014
1015 FormLayoutHelper() = default;
1016
1017 QRect itemInfo(QLayout *lt, int index) const override;
1018 void insertWidget(QLayout *lt, const QRect &info, QWidget *w) override;
1019 void removeWidget(QLayout *lt, QWidget *widget) override;
1020 void replaceWidget(QLayout *lt, QWidget *before, QWidget *after) override;
1021
1022 void pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout) override;
1023 void popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout) override;
1024
1025 bool canSimplify(const QDesignerFormEditorInterface *core, const QWidget *, const QRect &) const override;
1026 void simplify(const QDesignerFormEditorInterface *, QWidget *, const QRect &) override;
1027
1028 private:
1029 static FormLayoutState state(const QFormLayout *lt);
1030
1031 QStack<FormLayoutState> m_states;
1032 };
1033
1034 QRect FormLayoutHelper::itemInfo(QLayout * lt, int index) const
1035 {
1036 QFormLayout *form = qobject_cast<QFormLayout *>(object: lt);
1037 Q_ASSERT(form);
1038 int row, column, colspan;
1039 getFormLayoutItemPosition(formLayout: form, index, rowPtr: &row, columnPtr: &column, rowspanPtr: nullptr, colspanPtr: &colspan);
1040 return QRect(column, row, colspan, 1);
1041 }
1042
1043 void FormLayoutHelper::insertWidget(QLayout *lt, const QRect &info, QWidget *w)
1044 {
1045 if (debugLayout)
1046 qDebug() << "FormLayoutHelper::insertWidget:" << w << info;
1047 QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem.
1048 QFormLayout *formLayout = qobject_cast<QFormLayout *>(object: lt);
1049 Q_ASSERT(formLayout);
1050 // check if there are any nonspacer items? (Drop on 3rd column or drop of a multiselection
1051 // on an empty item. As the Form layout does not have insert semantics; we need to manually insert a row
1052 const bool insert = !removeEmptyCellsOnGrid(grid: formLayout, area: info);
1053 formLayoutAddWidget(formLayout, w, r: info, insert);
1054 QLayoutSupport::createEmptyCells(formLayout);
1055 }
1056
1057 void FormLayoutHelper::removeWidget(QLayout *lt, QWidget *widget)
1058 {
1059 QFormLayout *formLayout = qobject_cast<QFormLayout *>(object: lt);
1060 Q_ASSERT(formLayout);
1061 const int index = formLayout->indexOf(widget);
1062 if (index == -1) {
1063 qWarning() << "FormLayoutHelper::removeWidget : Attempt to remove " << widget << " which is not in the layout.";
1064 return;
1065 }
1066 // delete old item and pad with by spacer items
1067 int row, column, colspan;
1068 getFormLayoutItemPosition(formLayout, index, rowPtr: &row, columnPtr: &column, rowspanPtr: nullptr, colspanPtr: &colspan);
1069 if (debugLayout)
1070 qDebug() << "FormLayoutHelper::removeWidget: #" << index << widget << " at " << row << column << colspan;
1071 delete formLayout->takeAt(index);
1072 if (colspan > 1 || column == 0)
1073 formLayout->setItem(row, role: QFormLayout::LabelRole, item: createFormSpacer());
1074 if (colspan > 1 || column == 1)
1075 formLayout->setItem(row, role: QFormLayout::FieldRole, item: createFormSpacer());
1076 }
1077
1078 void FormLayoutHelper::replaceWidget(QLayout *lt, QWidget *before, QWidget *after)
1079 {
1080 bool ok = false;
1081 QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem.
1082 if (QFormLayout *formLayout = qobject_cast<QFormLayout *>(object: lt)) {
1083 const int index = formLayout->indexOf(before);
1084 if (index != -1) {
1085 int row;
1086 QFormLayout::ItemRole role;
1087 formLayout->getItemPosition (index, rowPtr: &row, rolePtr: &role);
1088 const bool visible = before->isVisible();
1089 delete formLayout->takeAt(index);
1090 if (visible)
1091 before->hide();
1092 before->setParent(nullptr);
1093 formLayout->setWidget(row, role, widget: after);
1094 ok = true;
1095 }
1096 }
1097 if (!ok)
1098 qWarning() << "FormLayoutHelper::replaceWidget : Unable to replace " << before << " by " << after << " in " << lt;
1099 }
1100
1101 FormLayoutHelper::FormLayoutState FormLayoutHelper::state(const QFormLayout *lt)
1102 {
1103 const int rowCount = lt->rowCount();
1104 if (rowCount == 0)
1105 return FormLayoutState();
1106 FormLayoutState rc(rowCount, WidgetPair(0, 0));
1107 const int count = lt->count();
1108 int row, column, colspan;
1109 for (int i = 0; i < count; i++) {
1110 QLayoutItem *item = lt->itemAt(index: i);
1111 if (!LayoutInfo::isEmptyItem(item)) {
1112 QWidget *w = item->widget();
1113 Q_ASSERT(w);
1114 getFormLayoutItemPosition(formLayout: lt, index: i, rowPtr: &row, columnPtr: &column, rowspanPtr: nullptr, colspanPtr: &colspan);
1115 if (colspan > 1 || column == 0)
1116 rc[row].first = w;
1117 if (colspan > 1 || column == 1)
1118 rc[row].second = w;
1119 }
1120 }
1121 if (debugLayout) {
1122 qDebug() << "FormLayoutHelper::state: " << rowCount;
1123 for (int r = 0; r < rowCount; r++)
1124 qDebug() << " Row: " << r << rc[r].first << rc[r].second;
1125 }
1126 return rc;
1127 }
1128
1129 void FormLayoutHelper::pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout)
1130 {
1131 QFormLayout *formLayout = qobject_cast<QFormLayout *>(object: LayoutInfo::managedLayout(core, widget: widgetWithManagedLayout));
1132 Q_ASSERT(formLayout);
1133 m_states.push(t: state(lt: formLayout));
1134 }
1135
1136 void FormLayoutHelper::popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout)
1137 {
1138 QFormLayout *formLayout = qobject_cast<QFormLayout *>(object: LayoutInfo::managedLayout(core, widget: widgetWithManagedLayout));
1139 Q_ASSERT(!m_states.isEmpty() && formLayout);
1140
1141 const FormLayoutState storedState = m_states.pop();
1142 const FormLayoutState currentState = state(lt: formLayout);
1143 if (currentState == storedState)
1144 return;
1145 const int rowCount = storedState.size();
1146 // clear out, shrink if required, but maintain items via map, pad spacers
1147 const BoxLayoutHelper::LayoutItemVector items = BoxLayoutHelper::disassembleLayout(lt: formLayout);
1148 if (rowCount < formLayout->rowCount())
1149 formLayout = static_cast<QFormLayout*>(recreateManagedLayout(core, w: widgetWithManagedLayout, lt: formLayout ));
1150 for (int r = 0; r < rowCount; r++) {
1151 QWidget *widgets[FormLayoutColumns] = { storedState[r].first, storedState[r].second };
1152 const bool spanning = widgets[0] != nullptr && widgets[0] == widgets[1];
1153 if (spanning) {
1154 formLayout->setWidget(row: r, role: QFormLayout::SpanningRole, widget: widgets[0]);
1155 } else {
1156 for (int c = 0; c < FormLayoutColumns; c++) {
1157 const QFormLayout::ItemRole role = c == 0 ? QFormLayout::LabelRole : QFormLayout::FieldRole;
1158 if (widgets[c] && BoxLayoutHelper::findItemOfWidget(lv: items, w: widgets[c])) {
1159 formLayout->setWidget(row: r, role, widget: widgets[c]);
1160 } else {
1161 formLayout->setItem(row: r, role, item: createFormSpacer());
1162 }
1163 }
1164 }
1165 }
1166 }
1167
1168 bool FormLayoutHelper::canSimplify(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout, const QRect &restrictionArea) const
1169 {
1170 const QFormLayout *formLayout = qobject_cast<QFormLayout *>(object: LayoutInfo::managedLayout(core, widget: widgetWithManagedLayout));
1171 Q_ASSERT(formLayout);
1172 return canSimplifyFormLayout(formLayout, restrictionArea);
1173 }
1174
1175 void FormLayoutHelper::simplify(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout, const QRect &restrictionArea)
1176 {
1177 using LayoutItemPair = QPair<QLayoutItem*, QLayoutItem*>;
1178 using LayoutItemPairs = QVector<LayoutItemPair>;
1179
1180 QFormLayout *formLayout = qobject_cast<QFormLayout *>(object: LayoutInfo::managedLayout(core, widget: widgetWithManagedLayout));
1181 Q_ASSERT(formLayout);
1182 if (debugLayout)
1183 qDebug() << "FormLayoutHelper::simplify";
1184 // Transform into vector of item pairs
1185 const int rowCount = formLayout->rowCount();
1186 LayoutItemPairs pairs(rowCount, LayoutItemPair(0, 0));
1187 for (int i = formLayout->count() - 1; i >= 0; i--) {
1188 int row, col,colspan;
1189 getFormLayoutItemPosition(formLayout, index: i, rowPtr: &row, columnPtr: &col, rowspanPtr: nullptr, colspanPtr: &colspan);
1190 if (colspan > 1) {
1191 pairs[row].first = pairs[row].second = formLayout->takeAt(index: i);
1192 } else {
1193 if (col == 0)
1194 pairs[row].first = formLayout->takeAt(index: i);
1195 else
1196 pairs[row].second = formLayout->takeAt(index: i);
1197 }
1198 }
1199 // Weed out empty ones
1200 const int bottomCheckRow = qMin(a: rowCount, b: restrictionArea.y() + restrictionArea.height());
1201 for (int r = bottomCheckRow - 1; r >= restrictionArea.y(); r--)
1202 if (LayoutInfo::isEmptyItem(item: pairs[r].first) && LayoutInfo::isEmptyItem(item: pairs[r].second)) {
1203 delete pairs[r].first;
1204 delete pairs[r].second;
1205 pairs.remove(i: r);
1206 }
1207 const int simpleRowCount = pairs.size();
1208 if (simpleRowCount < rowCount)
1209 formLayout = static_cast<QFormLayout *>(recreateManagedLayout(core, w: widgetWithManagedLayout, lt: formLayout));
1210 // repopulate
1211 for (int r = 0; r < simpleRowCount; r++) {
1212 const bool spanning = pairs[r].first == pairs[r].second;
1213 if (spanning) {
1214 formLayout->setItem(row: r, role: QFormLayout::SpanningRole, item: pairs[r].first);
1215 } else {
1216 formLayout->setItem(row: r, role: QFormLayout::LabelRole, item: pairs[r].first);
1217 formLayout->setItem(row: r, role: QFormLayout::FieldRole, item: pairs[r].second);
1218 }
1219 }
1220 }
1221
1222LayoutHelper *LayoutHelper::createLayoutHelper(int type)
1223{
1224 LayoutHelper *rc = nullptr;
1225 switch (type) {
1226 case LayoutInfo::HBox:
1227 rc = new BoxLayoutHelper(Qt::Horizontal);
1228 break;
1229 case LayoutInfo::VBox:
1230 rc = new BoxLayoutHelper(Qt::Vertical);
1231 break;
1232 case LayoutInfo::Grid:
1233 rc = new GridLayoutHelper;
1234 break;
1235 case LayoutInfo::Form:
1236 return new FormLayoutHelper;
1237 default:
1238 break;
1239 }
1240 Q_ASSERT(rc);
1241 return rc;
1242}
1243
1244// ---- QLayoutSupport (LayoutDecorationExtension)
1245QLayoutSupport::QLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, LayoutHelper *helper, QObject *parent) :
1246 QObject(parent),
1247 m_formWindow(formWindow),
1248 m_helper(helper),
1249 m_widget(widget),
1250 m_currentIndex(-1),
1251 m_currentInsertMode(QDesignerLayoutDecorationExtension::InsertWidgetMode)
1252{
1253}
1254
1255QLayout * QLayoutSupport::layout() const
1256{
1257 return LayoutInfo::managedLayout(core: m_formWindow->core(), widget: m_widget);
1258}
1259
1260void QLayoutSupport::hideIndicator(Indicator i)
1261{
1262 if (m_indicators[i])
1263 m_indicators[i]->hide();
1264}
1265
1266void QLayoutSupport::showIndicator(Indicator i, const QRect &geometry, const QPalette &p)
1267{
1268 if (!m_indicators[i])
1269 m_indicators[i] = new qdesigner_internal::InvisibleWidget(m_widget);
1270 QWidget *indicator = m_indicators[i];
1271 indicator->setAutoFillBackground(true);
1272 indicator->setPalette(p);
1273 indicator->setGeometry(geometry);
1274 indicator->show();
1275 indicator->raise();
1276}
1277
1278QLayoutSupport::~QLayoutSupport()
1279{
1280 delete m_helper;
1281 for (const QPointer<QWidget> &w : m_indicators) {
1282 if (!w.isNull())
1283 w->deleteLater();
1284 }
1285}
1286
1287QGridLayout * QLayoutSupport::gridLayout() const
1288{
1289 return qobject_cast<QGridLayout*>(object: LayoutInfo::managedLayout(core: m_formWindow->core(), widget: m_widget));
1290}
1291
1292QRect QLayoutSupport::itemInfo(int index) const
1293{
1294 return m_helper->itemInfo(lt: LayoutInfo::managedLayout(core: m_formWindow->core(), widget: m_widget), index);
1295}
1296
1297void QLayoutSupport::setInsertMode(InsertMode im)
1298{
1299 m_currentInsertMode = im;
1300}
1301
1302void QLayoutSupport::setCurrentCell(const QPair<int, int> &cell)
1303{
1304 m_currentCell = cell;
1305}
1306
1307void QLayoutSupport::adjustIndicator(const QPoint &pos, int index)
1308{
1309 if (index == -1) { // first item goes anywhere
1310 hideIndicator(i: LeftIndicator);
1311 hideIndicator(i: TopIndicator);
1312 hideIndicator(i: RightIndicator);
1313 hideIndicator(i: BottomIndicator);
1314 return;
1315 }
1316 m_currentIndex = index;
1317 m_currentInsertMode = QDesignerLayoutDecorationExtension::InsertWidgetMode;
1318
1319 QLayoutItem *item = layout()->itemAt(index);
1320 const QRect g = extendedGeometry(index);
1321 // ### cleanup
1322 if (LayoutInfo::isEmptyItem(item)) {
1323 // Empty grid/form cell. Draw a rectangle
1324 QPalette redPalette;
1325 redPalette.setColor(acr: QPalette::Window, acolor: Qt::red);
1326
1327 showIndicator(i: LeftIndicator, geometry: QRect(g.x(), g.y(), indicatorSize, g.height()), p: redPalette);
1328 showIndicator(i: TopIndicator, geometry: QRect(g.x(), g.y(), g.width(), indicatorSize), p: redPalette);
1329 showIndicator(i: RightIndicator, geometry: QRect(g.right(), g.y(), indicatorSize, g.height()), p: redPalette);
1330 showIndicator(i: BottomIndicator, geometry: QRect(g.x(), g.bottom(), g.width(), indicatorSize), p: redPalette);
1331 setCurrentCellFromIndicatorOnEmptyCell(m_currentIndex);
1332 } else {
1333 // Append/Insert. Draw a bar left/right or above/below
1334 QPalette bluePalette;
1335 bluePalette.setColor(acr: QPalette::Window, acolor: Qt::blue);
1336 hideIndicator(i: LeftIndicator);
1337 hideIndicator(i: TopIndicator);
1338
1339 const int fromRight = g.right() - pos.x();
1340 const int fromBottom = g.bottom() - pos.y();
1341
1342 const int fromLeft = pos.x() - g.x();
1343 const int fromTop = pos.y() - g.y();
1344
1345 const int fromLeftRight = qMin(a: fromRight, b: fromLeft );
1346 const int fromBottomTop = qMin(a: fromBottom, b: fromTop);
1347
1348 const Qt::Orientation indicatorOrientation = fromLeftRight < fromBottomTop ? Qt::Vertical : Qt::Horizontal;
1349
1350 if (supportsIndicatorOrientation(indicatorOrientation)) {
1351 const QRect r(layout()->geometry().topLeft(), layout()->parentWidget()->size());
1352 switch (indicatorOrientation) {
1353 case Qt::Vertical: {
1354 hideIndicator(i: BottomIndicator);
1355 const bool closeToLeft = fromLeftRight == fromLeft;
1356 showIndicator(i: RightIndicator, geometry: QRect(closeToLeft ? g.x() : g.right() + 1 - indicatorSize, 0, indicatorSize, r.height()), p: bluePalette);
1357
1358 const QWidget *parent = layout()->parentWidget();
1359 const bool leftToRight = Qt::LeftToRight == (parent ? parent->layoutDirection() : QApplication::layoutDirection());
1360 const int incr = leftToRight == closeToLeft ? 0 : +1;
1361 setCurrentCellFromIndicator(indicatorOrientation, index: m_currentIndex, increment: incr);
1362 }
1363 break;
1364 case Qt::Horizontal: {
1365 hideIndicator(i: RightIndicator);
1366 const bool closeToTop = fromBottomTop == fromTop;
1367 showIndicator(i: BottomIndicator, geometry: QRect(r.x(), closeToTop ? g.y() : g.bottom() + 1 - indicatorSize, r.width(), indicatorSize), p: bluePalette);
1368
1369 const int incr = closeToTop ? 0 : +1;
1370 setCurrentCellFromIndicator(indicatorOrientation, index: m_currentIndex, increment: incr);
1371 }
1372 break;
1373 }
1374 } else {
1375 hideIndicator(i: RightIndicator);
1376 hideIndicator(i: BottomIndicator);
1377 } // can handle indicatorOrientation
1378 }
1379}
1380
1381int QLayoutSupport::indexOf(QLayoutItem *i) const
1382{
1383 const QLayout *lt = layout();
1384 if (!lt)
1385 return -1;
1386
1387 int index = 0;
1388
1389 while (QLayoutItem *item = lt->itemAt(index)) {
1390 if (item == i)
1391 return index;
1392
1393 ++index;
1394 }
1395
1396 return -1;
1397}
1398
1399int QLayoutSupport::indexOf(QWidget *widget) const
1400{
1401 const QLayout *lt = layout();
1402 if (!lt)
1403 return -1;
1404
1405 int index = 0;
1406 while (QLayoutItem *item = lt->itemAt(index)) {
1407 if (item->widget() == widget)
1408 return index;
1409
1410 ++index;
1411 }
1412
1413 return -1;
1414}
1415
1416QWidgetList QLayoutSupport::widgets(QLayout *layout) const
1417{
1418 if (!layout)
1419 return QWidgetList();
1420
1421 QWidgetList lst;
1422 int index = 0;
1423 while (QLayoutItem *item = layout->itemAt(index)) {
1424 ++index;
1425
1426 QWidget *widget = item->widget();
1427 if (widget && formWindow()->isManaged(widget))
1428 lst.append(t: widget);
1429 }
1430
1431 return lst;
1432}
1433
1434int QLayoutSupport::findItemAt(QGridLayout *gridLayout, int at_row, int at_column)
1435{
1436 return findGridItemAt(gridLayout, at_row, at_column);
1437}
1438
1439// Quick check whether simplify should be enabled for grids. May return false positives.
1440// Note: Calculating the occupied area does not work as spanning items may also be simplified.
1441
1442bool QLayoutSupport::canSimplifyQuickCheck(const QGridLayout *gl)
1443{
1444 if (!gl)
1445 return false;
1446 const int colCount = gl->columnCount();
1447 const int rowCount = gl->rowCount();
1448 if (colCount < 2 || rowCount < 2)
1449 return false;
1450 // try to find a spacer.
1451 const int count = gl->count();
1452 for (int index = 0; index < count; index++)
1453 if (LayoutInfo::isEmptyItem(item: gl->itemAt(index)))
1454 return true;
1455 return false;
1456}
1457
1458bool QLayoutSupport::canSimplifyQuickCheck(const QFormLayout *fl)
1459{
1460 return canSimplifyFormLayout(formLayout: fl, restrictionArea: QRect(QPoint(0, 0), QSize(32767, 32767)));
1461}
1462
1463// remove dummy spacers
1464bool QLayoutSupport::removeEmptyCells(QGridLayout *grid, const QRect &area)
1465{
1466 return removeEmptyCellsOnGrid(grid, area);
1467}
1468
1469void QLayoutSupport::createEmptyCells(QGridLayout *gridLayout)
1470{
1471 Q_ASSERT(gridLayout);
1472 GridLayoutState gs;
1473 gs.fromLayout(l: gridLayout);
1474
1475 const GridLayoutState::CellStates cs = GridLayoutState::cellStates(rects: gs.widgetItemMap.values(), numRows: gs.rowCount, numColumns: gs.colCount);
1476 for (int c = 0; c < gs.colCount; c++)
1477 for (int r = 0; r < gs.rowCount; r++)
1478 if (needsSpacerItem(cs: cs[r * gs.colCount + c])) {
1479 const int existingItemIndex = findItemAt(gridLayout, at_row: r, at_column: c);
1480 if (existingItemIndex == -1)
1481 gridLayout->addItem(item: createGridSpacer(), row: r, column: c);
1482 }
1483}
1484
1485bool QLayoutSupport::removeEmptyCells(QFormLayout *formLayout, const QRect &area)
1486{
1487 return removeEmptyCellsOnGrid(grid: formLayout, area);
1488}
1489
1490void QLayoutSupport::createEmptyCells(QFormLayout *formLayout)
1491{
1492 // No spanning items here..
1493 if (const int rowCount = formLayout->rowCount())
1494 for (int c = 0; c < FormLayoutColumns; c++)
1495 for (int r = 0; r < rowCount; r++)
1496 if (findGridItemAt(gridLayout: formLayout, at_row: r, at_column: c) == -1)
1497 formLayout->setItem(row: r, role: c == 0 ? QFormLayout::LabelRole : QFormLayout::FieldRole, item: createFormSpacer());
1498}
1499
1500int QLayoutSupport::findItemAt(const QPoint &pos) const
1501{
1502 if (!layout())
1503 return -1;
1504
1505 const QLayout *lt = layout();
1506 const int count = lt->count();
1507
1508 if (count == 0)
1509 return -1;
1510
1511 int best = -1;
1512 int bestIndex = -1;
1513
1514 for (int index = 0; index < count; index++) {
1515 QLayoutItem *item = lt->itemAt(index);
1516 bool visible = true;
1517 // When dragging widgets within layout, the source widget is invisible and must not be hit
1518 if (const QWidget *w = item->widget())
1519 visible = w->isVisible();
1520 if (visible) {
1521 const QRect g = item->geometry();
1522
1523 const int dist = (g.center() - pos).manhattanLength();
1524 if (best == -1 || dist < best) {
1525 best = dist;
1526 bestIndex = index;
1527 }
1528 }
1529 }
1530 return bestIndex;
1531}
1532
1533// ------------ QBoxLayoutSupport (LayoutDecorationExtension)
1534namespace {
1535class QBoxLayoutSupport: public QLayoutSupport
1536{
1537public:
1538 QBoxLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, Qt::Orientation orientation, QObject *parent = nullptr);
1539
1540 void insertWidget(QWidget *widget, const QPair<int, int> &cell) override;
1541 void removeWidget(QWidget *widget) override;
1542 void simplify() override {}
1543 void insertRow(int /*row*/) override {}
1544 void insertColumn(int /*column*/) override {}
1545
1546 int findItemAt(int /*at_row*/, int /*at_column*/) const override { return -1; }
1547 using QLayoutSupport::findItemAt;
1548
1549private:
1550 void setCurrentCellFromIndicatorOnEmptyCell(int index) override;
1551 void setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) override;
1552 bool supportsIndicatorOrientation(Qt::Orientation indicatorOrientation) const override;
1553 QRect extendedGeometry(int index) const override;
1554
1555 const Qt::Orientation m_orientation;
1556};
1557
1558void QBoxLayoutSupport::removeWidget(QWidget *widget)
1559{
1560 QLayout *lt = layout();
1561 const int index = lt->indexOf(widget);
1562 // Adjust the current cell in case a widget was dragged within the same layout to a position
1563 // of higher index, which happens as follows:
1564 // Drag start: The widget is hidden
1565 // Drop: Current cell is stored, widget is removed and re-added, causing an index offset that needs to be compensated
1566 QPair<int, int> currCell = currentCell();
1567 switch (m_orientation) {
1568 case Qt::Horizontal:
1569 if (currCell.second > 0 && index < currCell.second ) {
1570 currCell.second--;
1571 setCurrentCell(currCell);
1572 }
1573 break;
1574 case Qt::Vertical:
1575 if (currCell.first > 0 && index < currCell.first) {
1576 currCell.first--;
1577 setCurrentCell(currCell);
1578 }
1579 break;
1580 }
1581 helper()->removeWidget(lt, widget);
1582}
1583
1584QBoxLayoutSupport::QBoxLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, Qt::Orientation orientation, QObject *parent) :
1585 QLayoutSupport(formWindow, widget, new BoxLayoutHelper(orientation), parent),
1586 m_orientation(orientation)
1587{
1588}
1589
1590void QBoxLayoutSupport::setCurrentCellFromIndicatorOnEmptyCell(int index)
1591{
1592 qDebug() << "QBoxLayoutSupport::setCurrentCellFromIndicatorOnEmptyCell(): Warning: found a fake spacer inside a vbox layout at " << index;
1593 setCurrentCell(qMakePair(x: 0, y: 0));
1594}
1595
1596void QBoxLayoutSupport::insertWidget(QWidget *widget, const QPair<int, int> &cell)
1597{
1598 switch (m_orientation) {
1599 case Qt::Horizontal:
1600 helper()->insertWidget(lt: layout(), info: QRect(cell.second, 0, 1, 1), w: widget);
1601 break;
1602 case Qt::Vertical:
1603 helper()->insertWidget(lt: layout(), info: QRect(0, cell.first, 1, 1), w: widget);
1604 break;
1605 }
1606}
1607
1608void QBoxLayoutSupport::setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment)
1609{
1610 if (m_orientation == Qt::Horizontal && indicatorOrientation == Qt::Vertical) {
1611 setCurrentCell(qMakePair(x: 0, y: index + increment));
1612 } else if (m_orientation == Qt::Vertical && indicatorOrientation == Qt::Horizontal) {
1613 setCurrentCell(qMakePair(x: index + increment, y: 0));
1614 }
1615}
1616
1617bool QBoxLayoutSupport::supportsIndicatorOrientation(Qt::Orientation indicatorOrientation) const
1618{
1619 return m_orientation != indicatorOrientation;
1620}
1621
1622QRect QBoxLayoutSupport::extendedGeometry(int index) const
1623{
1624 QLayoutItem *item = layout()->itemAt(index);
1625 // start off with item geometry
1626 QRect g = item->geometry();
1627
1628 const QRect info = itemInfo(index);
1629
1630 // On left border: extend to widget border
1631 if (info.x() == 0) {
1632 QPoint topLeft = g.topLeft();
1633 topLeft.rx() = layout()->geometry().left();
1634 g.setTopLeft(topLeft);
1635 }
1636
1637 // On top border: extend to widget border
1638 if (info.y() == 0) {
1639 QPoint topLeft = g.topLeft();
1640 topLeft.ry() = layout()->geometry().top();
1641 g.setTopLeft(topLeft);
1642 }
1643
1644 // is this the last item?
1645 const QBoxLayout *box = static_cast<const QBoxLayout*>(layout());
1646 if (index < box->count() -1)
1647 return g; // Nope.
1648
1649 // extend to widget border
1650 QPoint bottomRight = g.bottomRight();
1651 switch (m_orientation) {
1652 case Qt::Vertical:
1653 bottomRight.ry() = layout()->geometry().bottom();
1654 break;
1655 case Qt::Horizontal:
1656 bottomRight.rx() = layout()->geometry().right();
1657 break;
1658 }
1659 g.setBottomRight(bottomRight);
1660 return g;
1661}
1662
1663// -------------- Base class for QGridLayout-like support classes (LayoutDecorationExtension)
1664template <class GridLikeLayout>
1665class GridLikeLayoutSupportBase: public QLayoutSupport
1666{
1667public:
1668
1669 GridLikeLayoutSupportBase(QDesignerFormWindowInterface *formWindow, QWidget *widget, LayoutHelper *helper, QObject *parent = nullptr) :
1670 QLayoutSupport(formWindow, widget, helper, parent) {}
1671
1672 void insertWidget(QWidget *widget, const QPair<int, int> &cell) override;
1673 void removeWidget(QWidget *widget) override { helper()->removeWidget(layout(), widget); }
1674 int findItemAt(int row, int column) const override;
1675 using QLayoutSupport::findItemAt;
1676
1677protected:
1678 GridLikeLayout *gridLikeLayout() const {
1679 return qobject_cast<GridLikeLayout*>(LayoutInfo::managedLayout(formWindow()->core(), widget()));
1680 }
1681
1682private:
1683
1684 void setCurrentCellFromIndicatorOnEmptyCell(int index) override;
1685 void setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) override;
1686 bool supportsIndicatorOrientation(Qt::Orientation) const override { return true; }
1687
1688 QRect extendedGeometry(int index) const override;
1689
1690 // Overwrite to check the insertion position (if there are limits)
1691 virtual void checkCellForInsertion(int * /*row*/, int * /*col*/) const {}
1692};
1693
1694template <class GridLikeLayout>
1695void GridLikeLayoutSupportBase<GridLikeLayout>::setCurrentCellFromIndicatorOnEmptyCell(int index)
1696{
1697 GridLikeLayout *grid = gridLikeLayout();
1698 Q_ASSERT(grid);
1699
1700 setInsertMode(InsertWidgetMode);
1701 int row, column, rowspan, colspan;
1702
1703 getGridItemPosition(grid, index, &row, &column, &rowspan, &colspan);
1704 setCurrentCell(qMakePair(x: row, y: column));
1705}
1706
1707template <class GridLikeLayout>
1708void GridLikeLayoutSupportBase<GridLikeLayout>::setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) {
1709 const QRect info = itemInfo(index);
1710 switch (indicatorOrientation) {
1711 case Qt::Vertical: {
1712 setInsertMode(InsertColumnMode);
1713 int row = info.top();
1714 int column = increment ? info.right() + 1 : info.left();
1715 checkCellForInsertion(&row, &column);
1716 setCurrentCell(qMakePair(x: row , y: column));
1717 }
1718 break;
1719 case Qt::Horizontal: {
1720 setInsertMode(InsertRowMode);
1721 int row = increment ? info.bottom() + 1 : info.top();
1722 int column = info.left();
1723 checkCellForInsertion(&row, &column);
1724 setCurrentCell(qMakePair(x: row, y: column));
1725 }
1726 break;
1727 }
1728}
1729
1730template <class GridLikeLayout>
1731void GridLikeLayoutSupportBase<GridLikeLayout>::insertWidget(QWidget *widget, const QPair<int, int> &cell)
1732{
1733 helper()->insertWidget(layout(), QRect(cell.second, cell.first, 1, 1), widget);
1734}
1735
1736template <class GridLikeLayout>
1737int GridLikeLayoutSupportBase<GridLikeLayout>::findItemAt(int at_row, int at_column) const
1738{
1739 GridLikeLayout *grid = gridLikeLayout();
1740 Q_ASSERT(grid);
1741 return findGridItemAt(grid, at_row, at_column);
1742}
1743
1744template <class GridLikeLayout>
1745QRect GridLikeLayoutSupportBase<GridLikeLayout>::extendedGeometry(int index) const
1746{
1747 QLayoutItem *item = layout()->itemAt(index);
1748 // start off with item geometry
1749 QRect g = item->geometry();
1750
1751 const QRect info = itemInfo(index);
1752
1753 // On left border: extend to widget border
1754 if (info.x() == 0) {
1755 QPoint topLeft = g.topLeft();
1756 topLeft.rx() = layout()->geometry().left();
1757 g.setTopLeft(topLeft);
1758 }
1759
1760 // On top border: extend to widget border
1761 if (info.y() == 0) {
1762 QPoint topLeft = g.topLeft();
1763 topLeft.ry() = layout()->geometry().top();
1764 g.setTopLeft(topLeft);
1765 }
1766 const GridLikeLayout *grid = gridLikeLayout();
1767 Q_ASSERT(grid);
1768
1769 // extend to widget border
1770 QPoint bottomRight = g.bottomRight();
1771 if (gridRowCount(grid) == info.y())
1772 bottomRight.ry() = layout()->geometry().bottom();
1773 if (gridColumnCount(grid) == info.x())
1774 bottomRight.rx() = layout()->geometry().right();
1775 g.setBottomRight(bottomRight);
1776 return g;
1777}
1778
1779// -------------- QGridLayoutSupport (LayoutDecorationExtension)
1780class QGridLayoutSupport: public GridLikeLayoutSupportBase<QGridLayout>
1781{
1782public:
1783
1784 QGridLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent = nullptr);
1785
1786 void simplify() override;
1787 void insertRow(int row) override;
1788 void insertColumn(int column) override;
1789
1790private:
1791};
1792
1793QGridLayoutSupport::QGridLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent) :
1794 GridLikeLayoutSupportBase<QGridLayout>(formWindow, widget, new GridLayoutHelper, parent)
1795{
1796}
1797
1798void QGridLayoutSupport::insertRow(int row)
1799{
1800 QGridLayout *grid = gridLayout();
1801 Q_ASSERT(grid);
1802 GridLayoutHelper::insertRow(grid, row);
1803}
1804
1805void QGridLayoutSupport::insertColumn(int column)
1806{
1807 QGridLayout *grid = gridLayout();
1808 Q_ASSERT(grid);
1809 GridLayoutState state;
1810 state.fromLayout(l: grid);
1811 state.insertColumn(column);
1812 state.applyToLayout(core: formWindow()->core(), w: widget());
1813}
1814
1815void QGridLayoutSupport::simplify()
1816{
1817 QGridLayout *grid = gridLayout();
1818 Q_ASSERT(grid);
1819 GridLayoutState state;
1820 state.fromLayout(l: grid);
1821
1822 const QRect fullArea = QRect(0, 0, state.colCount, state.rowCount);
1823 if (state.simplify(r: fullArea, testOnly: false))
1824 state.applyToLayout(core: formWindow()->core(), w: widget());
1825}
1826
1827// -------------- QFormLayoutSupport (LayoutDecorationExtension)
1828class QFormLayoutSupport: public GridLikeLayoutSupportBase<QFormLayout>
1829{
1830public:
1831 QFormLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent = nullptr);
1832
1833 void simplify() override {}
1834 void insertRow(int /*row*/) override {}
1835 void insertColumn(int /*column*/) override {}
1836
1837private:
1838 void checkCellForInsertion(int * row, int *col) const override;
1839};
1840
1841QFormLayoutSupport::QFormLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent) :
1842 GridLikeLayoutSupportBase<QFormLayout>(formWindow, widget, new FormLayoutHelper, parent)
1843{
1844}
1845
1846void QFormLayoutSupport::checkCellForInsertion(int *row, int *col) const
1847{
1848 if (*col >= FormLayoutColumns) { // Clamp to 2 columns
1849 *col = 1;
1850 (*row)++;
1851 }
1852}
1853} // anonymous namespace
1854
1855QLayoutSupport *QLayoutSupport::createLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent)
1856{
1857 const QLayout *layout = LayoutInfo::managedLayout(core: formWindow->core(), widget);
1858 Q_ASSERT(layout);
1859 QLayoutSupport *rc = nullptr;
1860 switch (LayoutInfo::layoutType(core: formWindow->core(), layout)) {
1861 case LayoutInfo::HBox:
1862 rc = new QBoxLayoutSupport(formWindow, widget, Qt::Horizontal, parent);
1863 break;
1864 case LayoutInfo::VBox:
1865 rc = new QBoxLayoutSupport(formWindow, widget, Qt::Vertical, parent);
1866 break;
1867 case LayoutInfo::Grid:
1868 rc = new QGridLayoutSupport(formWindow, widget, parent);
1869 break;
1870 case LayoutInfo::Form:
1871 rc = new QFormLayoutSupport(formWindow, widget, parent);
1872 break;
1873 default:
1874 break;
1875 }
1876 Q_ASSERT(rc);
1877 return rc;
1878}
1879} // namespace qdesigner_internal
1880
1881// -------------- QLayoutWidget
1882QLayoutWidget::QLayoutWidget(QDesignerFormWindowInterface *formWindow, QWidget *parent)
1883 : QWidget(parent), m_formWindow(formWindow),
1884 m_leftMargin(0), m_topMargin(0), m_rightMargin(0), m_bottomMargin(0)
1885{
1886}
1887
1888void QLayoutWidget::paintEvent(QPaintEvent*)
1889{
1890 if (m_formWindow->currentTool() != 0)
1891 return;
1892
1893 // only draw red borders if we're editting widgets
1894
1895 QPainter p(this);
1896
1897 QMap<int, QMap<int, bool> > excludedRowsForColumn;
1898 QMap<int, QMap<int, bool> > excludedColumnsForRow;
1899
1900 QLayout *lt = layout();
1901 QGridLayout *grid = qobject_cast<QGridLayout *>(object: lt);
1902 if (lt) {
1903 if (const int count = lt->count()) {
1904 p.setPen(QPen(QColor(255, 0, 0, 35), 1));
1905 for (int i = 0; i < count; i++) {
1906 QLayoutItem *item = lt->itemAt(index: i);
1907 if (grid) {
1908 int row, column, rowSpan, columnSpan;
1909 grid->getItemPosition(idx: i, row: &row, column: &column, rowSpan: &rowSpan, columnSpan: &columnSpan);
1910 QMap<int, bool> rows;
1911 QMap<int, bool> columns;
1912 for (int i = rowSpan; i > 1; i--)
1913 rows[row + i - 2] = true;
1914 for (int i = columnSpan; i > 1; i--)
1915 columns[column + i - 2] = true;
1916
1917 while (rowSpan > 0) {
1918 excludedColumnsForRow[row + rowSpan - 1].insert(map: columns);
1919 rowSpan--;
1920 }
1921 while (columnSpan > 0) {
1922 excludedRowsForColumn[column + columnSpan - 1].insert(map: rows);
1923 columnSpan--;
1924 }
1925 }
1926 if (item->spacerItem()) {
1927 const QRect geometry = item->geometry();
1928 if (!geometry.isNull())
1929 p.drawRect(r: geometry.adjusted(xp1: 1, yp1: 1, xp2: -2, yp2: -2));
1930 }
1931 }
1932 }
1933 }
1934 if (grid) {
1935 p.setPen(QPen(QColor(0, 0x80, 0, 0x80), 1));
1936 const int rowCount = grid->rowCount();
1937 const int columnCount = grid->columnCount();
1938 for (int i = 0; i < rowCount; i++) {
1939 for (int j = 0; j < columnCount; j++) {
1940 const QRect cellRect = grid->cellRect(row: i, column: j);
1941 if (j < columnCount - 1 && !excludedColumnsForRow.value(akey: i).value(akey: j, adefaultValue: false)) {
1942 const double y0 = (i == 0)
1943 ? 0 : (grid->cellRect(row: i - 1, column: j).bottom() + cellRect.top()) / 2.0;
1944 const double y1 = (i == rowCount - 1)
1945 ? height() - 1 : (cellRect.bottom() + grid->cellRect(row: i + 1, column: j).top()) / 2.0;
1946 const double x = (cellRect.right() + grid->cellRect(row: i, column: j + 1).left()) / 2.0;
1947 p.drawLine(p1: QPointF(x, y0), p2: QPointF(x, y1));
1948 }
1949 if (i < rowCount - 1 && !excludedRowsForColumn.value(akey: j).value(akey: i, adefaultValue: false)) {
1950 const double x0 = (j == 0)
1951 ? 0 : (grid->cellRect(row: i, column: j - 1).right() + cellRect.left()) / 2.0;
1952 const double x1 = (j == columnCount - 1)
1953 ? width() - 1 : (cellRect.right() + grid->cellRect(row: i, column: j + 1).left()) / 2.0;
1954 const double y = (cellRect.bottom() + grid->cellRect(row: i + 1, column: j).top()) / 2.0;
1955 p.drawLine(p1: QPointF(x0, y), p2: QPointF(x1, y));
1956 }
1957 }
1958 }
1959 }
1960 p.setPen(QPen(QColor(255, 0, 0, 128), 1));
1961 p.drawRect(x: 0, y: 0, w: width() - 1, h: height() - 1);
1962}
1963
1964bool QLayoutWidget::event(QEvent *e)
1965{
1966 switch (e->type()) {
1967 case QEvent::LayoutRequest: {
1968 (void) QWidget::event(event: e);
1969 // Magic: We are layouted, but the parent is not..
1970 if (layout() && qdesigner_internal::LayoutInfo::layoutType(core: formWindow()->core(), w: parentWidget()) == qdesigner_internal::LayoutInfo::NoLayout) {
1971 resize(layout()->totalMinimumSize().expandedTo(otherSize: size()));
1972 }
1973
1974 update();
1975
1976 return true;
1977 }
1978
1979 default:
1980 break;
1981 }
1982
1983 return QWidget::event(event: e);
1984}
1985
1986int QLayoutWidget::layoutLeftMargin() const
1987{
1988 if (m_leftMargin < 0 && layout()) {
1989 int margin;
1990 layout()->getContentsMargins(left: &margin, top: nullptr, right: nullptr, bottom: nullptr);
1991 return margin;
1992 }
1993 return m_leftMargin;
1994}
1995
1996void QLayoutWidget::setLayoutLeftMargin(int layoutMargin)
1997{
1998 m_leftMargin = layoutMargin;
1999 if (layout()) {
2000 int newMargin = m_leftMargin;
2001 if (newMargin >= 0 && newMargin < ShiftValue)
2002 newMargin = ShiftValue;
2003 int left, top, right, bottom;
2004 layout()->getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
2005 layout()->setContentsMargins(left: newMargin, top, right, bottom);
2006 }
2007}
2008
2009int QLayoutWidget::layoutTopMargin() const
2010{
2011 if (m_topMargin < 0 && layout()) {
2012 int margin;
2013 layout()->getContentsMargins(left: nullptr, top: &margin, right: nullptr, bottom: nullptr);
2014 return margin;
2015 }
2016 return m_topMargin;
2017}
2018
2019void QLayoutWidget::setLayoutTopMargin(int layoutMargin)
2020{
2021 m_topMargin = layoutMargin;
2022 if (layout()) {
2023 int newMargin = m_topMargin;
2024 if (newMargin >= 0 && newMargin < ShiftValue)
2025 newMargin = ShiftValue;
2026 int left, top, right, bottom;
2027 layout()->getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
2028 layout()->setContentsMargins(left, top: newMargin, right, bottom);
2029 }
2030}
2031
2032int QLayoutWidget::layoutRightMargin() const
2033{
2034 if (m_rightMargin < 0 && layout()) {
2035 int margin;
2036 layout()->getContentsMargins(left: nullptr, top: nullptr, right: &margin, bottom: nullptr);
2037 return margin;
2038 }
2039 return m_rightMargin;
2040}
2041
2042void QLayoutWidget::setLayoutRightMargin(int layoutMargin)
2043{
2044 m_rightMargin = layoutMargin;
2045 if (layout()) {
2046 int newMargin = m_rightMargin;
2047 if (newMargin >= 0 && newMargin < ShiftValue)
2048 newMargin = ShiftValue;
2049 int left, top, right, bottom;
2050 layout()->getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
2051 layout()->setContentsMargins(left, top, right: newMargin, bottom);
2052 }
2053}
2054
2055int QLayoutWidget::layoutBottomMargin() const
2056{
2057 if (m_bottomMargin < 0 && layout()) {
2058 int margin;
2059 layout()->getContentsMargins(left: nullptr, top: nullptr, right: nullptr, bottom: &margin);
2060 return margin;
2061 }
2062 return m_bottomMargin;
2063}
2064
2065void QLayoutWidget::setLayoutBottomMargin(int layoutMargin)
2066{
2067 m_bottomMargin = layoutMargin;
2068 if (layout()) {
2069 int newMargin = m_bottomMargin;
2070 if (newMargin >= 0 && newMargin < ShiftValue)
2071 newMargin = ShiftValue;
2072 int left, top, right, bottom;
2073 layout()->getContentsMargins(left: &left, top: &top, right: &right, bottom: &bottom);
2074 layout()->setContentsMargins(left, top, right, bottom: newMargin);
2075 }
2076}
2077
2078QT_END_NAMESPACE
2079

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