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 | |
58 | enum { ShiftValue = 1 }; |
59 | enum { debugLayout = 0 }; |
60 | enum { FormLayoutColumns = 2 }; |
61 | enum { indicatorSize = 2 }; |
62 | // Grid/form Helpers: get info (overloads to make templates work) |
63 | |
64 | namespace { // Do not use static, will break HP-UX due to templates |
65 | |
66 | QT_USE_NAMESPACE |
67 | |
68 | // overloads to make templates over QGridLayout/QFormLayout work |
69 | inline int gridRowCount(const QGridLayout *gridLayout) |
70 | { |
71 | return gridLayout->rowCount(); |
72 | } |
73 | |
74 | inline int gridColumnCount(const QGridLayout *gridLayout) |
75 | { |
76 | return gridLayout->columnCount(); |
77 | } |
78 | |
79 | // QGridLayout/QFormLayout Helpers: get item position (overloads to make templates work) |
80 | inline 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 | |
86 | QRect 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 | |
94 | inline int gridRowCount(const QFormLayout *formLayout) { return formLayout->rowCount(); } |
95 | inline int gridColumnCount(const QFormLayout *) { return FormLayoutColumns; } |
96 | |
97 | inline 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 | |
103 | QT_BEGIN_NAMESPACE |
104 | |
105 | static const char *objectNameC = "objectName" ; |
106 | static 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 */ |
110 | namespace 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 | |
120 | static inline QSpacerItem *createGridSpacer() |
121 | { |
122 | return new QSpacerItem(0, 0); |
123 | } |
124 | |
125 | static inline QSpacerItem *createFormSpacer() |
126 | { |
127 | return new qdesigner_internal::PaddingSpacerItem; |
128 | } |
129 | |
130 | // QGridLayout/QFormLayout Helpers: Debug items of GridLikeLayout |
131 | template <class GridLikeLayout> |
132 | static 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 | |
144 | static inline QDebug operator<<(QDebug str, const QGridLayout &gl) { return debugGridLikeLayout(str, gl); } |
145 | |
146 | static 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 | |
154 | static 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 | |
169 | static 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 |
181 | template <class GridLikeLayout> |
182 | int 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 |
197 | template <class GridLikeLayout> |
198 | static 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 | |
228 | namespace qdesigner_internal { |
229 | // --------- LayoutProperties |
230 | |
231 | LayoutProperties::LayoutProperties() |
232 | { |
233 | clear(); |
234 | } |
235 | |
236 | void 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 | |
255 | int 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 | |
276 | static const char *marginPropertyNamesC[] = {"leftMargin" , "topMargin" , "rightMargin" , "bottomMargin" }; |
277 | static const char *spacingPropertyNamesC[] = {"spacing" , "horizontalSpacing" , "verticalSpacing" }; |
278 | static const char *fieldGrowthPolicyPropertyC = "fieldGrowthPolicy" ; |
279 | static const char *rowWrapPolicyPropertyC = "rowWrapPolicy" ; |
280 | static const char *labelAlignmentPropertyC = "labelAlignment" ; |
281 | static const char *formAlignmentPropertyC = "formAlignment" ; |
282 | static const char *boxStretchPropertyC = "stretch" ; |
283 | static const char *gridRowStretchPropertyC = "rowStretch" ; |
284 | static const char *gridColumnStretchPropertyC = "columnStretch" ; |
285 | static const char *gridRowMinimumHeightPropertyC = "rowMinimumHeight" ; |
286 | static const char *gridColumnMinimumWidthPropertyC = "columnMinimumWidth" ; |
287 | |
288 | static 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 | |
298 | static 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 | |
311 | int 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 | |
350 | static 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 | |
365 | static 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 | |
379 | int 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 |
420 | LayoutHelper::LayoutHelper() = default; |
421 | |
422 | LayoutHelper::~LayoutHelper() = default; |
423 | |
424 | int 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 | |
436 | QRect 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 | |
1222 | LayoutHelper *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) |
1245 | QLayoutSupport::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 | |
1255 | QLayout * QLayoutSupport::layout() const |
1256 | { |
1257 | return LayoutInfo::managedLayout(core: m_formWindow->core(), widget: m_widget); |
1258 | } |
1259 | |
1260 | void QLayoutSupport::hideIndicator(Indicator i) |
1261 | { |
1262 | if (m_indicators[i]) |
1263 | m_indicators[i]->hide(); |
1264 | } |
1265 | |
1266 | void 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 | |
1278 | QLayoutSupport::~QLayoutSupport() |
1279 | { |
1280 | delete m_helper; |
1281 | for (const QPointer<QWidget> &w : m_indicators) { |
1282 | if (!w.isNull()) |
1283 | w->deleteLater(); |
1284 | } |
1285 | } |
1286 | |
1287 | QGridLayout * QLayoutSupport::gridLayout() const |
1288 | { |
1289 | return qobject_cast<QGridLayout*>(object: LayoutInfo::managedLayout(core: m_formWindow->core(), widget: m_widget)); |
1290 | } |
1291 | |
1292 | QRect QLayoutSupport::itemInfo(int index) const |
1293 | { |
1294 | return m_helper->itemInfo(lt: LayoutInfo::managedLayout(core: m_formWindow->core(), widget: m_widget), index); |
1295 | } |
1296 | |
1297 | void QLayoutSupport::setInsertMode(InsertMode im) |
1298 | { |
1299 | m_currentInsertMode = im; |
1300 | } |
1301 | |
1302 | void QLayoutSupport::setCurrentCell(const QPair<int, int> &cell) |
1303 | { |
1304 | m_currentCell = cell; |
1305 | } |
1306 | |
1307 | void 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 | |
1381 | int 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 | |
1399 | int 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 | |
1416 | QWidgetList 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 | |
1434 | int 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 | |
1442 | bool 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 | |
1458 | bool 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 |
1464 | bool QLayoutSupport::removeEmptyCells(QGridLayout *grid, const QRect &area) |
1465 | { |
1466 | return removeEmptyCellsOnGrid(grid, area); |
1467 | } |
1468 | |
1469 | void 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 | |
1485 | bool QLayoutSupport::removeEmptyCells(QFormLayout *formLayout, const QRect &area) |
1486 | { |
1487 | return removeEmptyCellsOnGrid(grid: formLayout, area); |
1488 | } |
1489 | |
1490 | void 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 | |
1500 | int 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) |
1534 | namespace { |
1535 | class QBoxLayoutSupport: public QLayoutSupport |
1536 | { |
1537 | public: |
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 | |
1549 | private: |
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 | |
1558 | void 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 | |
1584 | QBoxLayoutSupport::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 | |
1590 | void 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 | |
1596 | void 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 | |
1608 | void 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 | |
1617 | bool QBoxLayoutSupport::supportsIndicatorOrientation(Qt::Orientation indicatorOrientation) const |
1618 | { |
1619 | return m_orientation != indicatorOrientation; |
1620 | } |
1621 | |
1622 | QRect 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) |
1664 | template <class GridLikeLayout> |
1665 | class GridLikeLayoutSupportBase: public QLayoutSupport |
1666 | { |
1667 | public: |
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 | |
1677 | protected: |
1678 | GridLikeLayout *gridLikeLayout() const { |
1679 | return qobject_cast<GridLikeLayout*>(LayoutInfo::managedLayout(formWindow()->core(), widget())); |
1680 | } |
1681 | |
1682 | private: |
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 | |
1694 | template <class GridLikeLayout> |
1695 | void 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 | |
1707 | template <class GridLikeLayout> |
1708 | void 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 | |
1730 | template <class GridLikeLayout> |
1731 | void 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 | |
1736 | template <class GridLikeLayout> |
1737 | int 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 | |
1744 | template <class GridLikeLayout> |
1745 | QRect 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) |
1780 | class QGridLayoutSupport: public GridLikeLayoutSupportBase<QGridLayout> |
1781 | { |
1782 | public: |
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 | |
1790 | private: |
1791 | }; |
1792 | |
1793 | QGridLayoutSupport::QGridLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent) : |
1794 | GridLikeLayoutSupportBase<QGridLayout>(formWindow, widget, new GridLayoutHelper, parent) |
1795 | { |
1796 | } |
1797 | |
1798 | void QGridLayoutSupport::insertRow(int row) |
1799 | { |
1800 | QGridLayout *grid = gridLayout(); |
1801 | Q_ASSERT(grid); |
1802 | GridLayoutHelper::insertRow(grid, row); |
1803 | } |
1804 | |
1805 | void 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 | |
1815 | void 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) |
1828 | class QFormLayoutSupport: public GridLikeLayoutSupportBase<QFormLayout> |
1829 | { |
1830 | public: |
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 | |
1837 | private: |
1838 | void checkCellForInsertion(int * row, int *col) const override; |
1839 | }; |
1840 | |
1841 | QFormLayoutSupport::QFormLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent) : |
1842 | GridLikeLayoutSupportBase<QFormLayout>(formWindow, widget, new FormLayoutHelper, parent) |
1843 | { |
1844 | } |
1845 | |
1846 | void 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 | |
1855 | QLayoutSupport *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 |
1882 | QLayoutWidget::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 | |
1888 | void 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 | |
1964 | bool 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 | |
1986 | int 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 | |
1996 | void 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 | |
2009 | int 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 | |
2019 | void 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 | |
2032 | int 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 | |
2042 | void 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 | |
2055 | int 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 | |
2065 | void 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 | |
2078 | QT_END_NAMESPACE |
2079 | |