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