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