1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2015 Olivier Goffart <ogoffart@woboq.com>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qmainwindowlayout_p.h"
6
7#if QT_CONFIG(dockwidget)
8#include "qdockarealayout_p.h"
9#include "qdockwidget.h"
10#include "qdockwidget_p.h"
11#endif
12#if QT_CONFIG(toolbar)
13#include "qtoolbar_p.h"
14#include "qtoolbar.h"
15#include "qtoolbarlayout_p.h"
16#endif
17#include "qmainwindow.h"
18#include "qwidgetanimator_p.h"
19#if QT_CONFIG(rubberband)
20#include "qrubberband.h"
21#endif
22#if QT_CONFIG(tabbar)
23#include "qtabbar_p.h"
24#endif
25
26#include <qapplication.h>
27#if QT_CONFIG(draganddrop)
28#include <qdrag.h>
29#endif
30#include <qmimedata.h>
31#if QT_CONFIG(statusbar)
32#include <qstatusbar.h>
33#endif
34#include <qstring.h>
35#include <qstyle.h>
36#include <qstylepainter.h>
37#include <qvarlengtharray.h>
38#include <qstack.h>
39#include <qmap.h>
40#include <qtimer.h>
41#include <qpointer.h>
42
43#ifndef QT_NO_DEBUG_STREAM
44# include <qdebug.h>
45# include <qtextstream.h>
46#endif
47
48#include <private/qmenu_p.h>
49#include <private/qapplication_p.h>
50#include <private/qlayoutengine_p.h>
51#include <private/qwidgetresizehandler_p.h>
52
53#include <QScopedValueRollback>
54
55QT_BEGIN_NAMESPACE
56
57using namespace Qt::StringLiterals;
58
59extern QMainWindowLayout *qt_mainwindow_layout(const QMainWindow *window);
60
61/******************************************************************************
62** debug
63*/
64
65#if QT_CONFIG(dockwidget) && !defined(QT_NO_DEBUG_STREAM)
66
67static void dumpLayout(QTextStream &qout, const QDockAreaLayoutInfo &layout, QString indent);
68
69static void dumpLayout(QTextStream &qout, const QDockAreaLayoutItem &item, QString indent)
70{
71 qout << indent << "QDockAreaLayoutItem: "
72 << "pos: " << item.pos << " size:" << item.size
73 << " gap:" << (item.flags & QDockAreaLayoutItem::GapItem)
74 << " keepSize:" << (item.flags & QDockAreaLayoutItem::KeepSize) << '\n';
75 indent += " "_L1;
76 if (item.widgetItem != nullptr) {
77 qout << indent << "widget: "
78 << item.widgetItem->widget()->metaObject()->className()
79 << " \"" << item.widgetItem->widget()->windowTitle() << "\"\n";
80 } else if (item.subinfo != nullptr) {
81 qout << indent << "subinfo:\n";
82 dumpLayout(qout, layout: *item.subinfo, indent: indent + " "_L1);
83 } else if (item.placeHolderItem != nullptr) {
84 QRect r = item.placeHolderItem->topLevelRect;
85 qout << indent << "placeHolder: "
86 << "pos: " << item.pos << " size:" << item.size
87 << " gap:" << (item.flags & QDockAreaLayoutItem::GapItem)
88 << " keepSize:" << (item.flags & QDockAreaLayoutItem::KeepSize)
89 << " objectName:" << item.placeHolderItem->objectName
90 << " hidden:" << item.placeHolderItem->hidden
91 << " window:" << item.placeHolderItem->window
92 << " rect:" << r.x() << ',' << r.y() << ' '
93 << r.width() << 'x' << r.height() << '\n';
94 }
95}
96
97static void dumpLayout(QTextStream &qout, const QDockAreaLayoutInfo &layout, QString indent)
98{
99 const QSize minSize = layout.minimumSize();
100 qout << indent << "QDockAreaLayoutInfo: "
101 << layout.rect.left() << ','
102 << layout.rect.top() << ' '
103 << layout.rect.width() << 'x'
104 << layout.rect.height()
105 << " min size: " << minSize.width() << ',' << minSize.height()
106 << " orient:" << layout.o
107#if QT_CONFIG(tabbar)
108 << " tabbed:" << layout.tabbed
109 << " tbshape:" << layout.tabBarShape
110#endif
111 << '\n';
112
113 indent += " "_L1;
114
115 for (int i = 0; i < layout.item_list.size(); ++i) {
116 qout << indent << "Item: " << i << '\n';
117 dumpLayout(qout, item: layout.item_list.at(i), indent: indent + " "_L1);
118 }
119}
120
121static void dumpLayout(QTextStream &qout, const QDockAreaLayout &layout)
122{
123 qout << "QDockAreaLayout: "
124 << layout.rect.left() << ','
125 << layout.rect.top() << ' '
126 << layout.rect.width() << 'x'
127 << layout.rect.height() << '\n';
128
129 qout << "TopDockArea:\n";
130 dumpLayout(qout, layout: layout.docks[QInternal::TopDock], indent: " "_L1);
131 qout << "LeftDockArea:\n";
132 dumpLayout(qout, layout: layout.docks[QInternal::LeftDock], indent: " "_L1);
133 qout << "RightDockArea:\n";
134 dumpLayout(qout, layout: layout.docks[QInternal::RightDock], indent: " "_L1);
135 qout << "BottomDockArea:\n";
136 dumpLayout(qout, layout: layout.docks[QInternal::BottomDock], indent: " "_L1);
137}
138
139QDebug operator<<(QDebug debug, const QDockAreaLayout &layout)
140{
141 QString s;
142 QTextStream str(&s);
143 dumpLayout(qout&: str, layout);
144 debug << s;
145 return debug;
146}
147
148QDebug operator<<(QDebug debug, const QMainWindowLayout *layout)
149{
150 debug << layout->layoutState.dockAreaLayout;
151 return debug;
152}
153
154#endif // QT_CONFIG(dockwidget) && !defined(QT_NO_DEBUG)
155
156/******************************************************************************
157 ** QDockWidgetGroupWindow
158 */
159// QDockWidgetGroupWindow is the floating window containing several QDockWidgets floating together.
160// (QMainWindow::GroupedDragging feature)
161// QDockWidgetGroupLayout is the layout of that window and use a QDockAreaLayoutInfo to layout
162// the QDockWidgets inside it.
163// If there is only one QDockWidgets, or all QDockWidgets are tabbed together, it is equivalent
164// of a floating QDockWidget (the title of the QDockWidget is the title of the window). But if there
165// are nested QDockWidget, an additional title bar is there.
166#if QT_CONFIG(dockwidget)
167class QDockWidgetGroupLayout : public QLayout,
168 public QMainWindowLayoutSeparatorHelper<QDockWidgetGroupLayout>
169{
170 QWidgetResizeHandler *resizer;
171public:
172 QDockWidgetGroupLayout(QDockWidgetGroupWindow* parent) : QLayout(parent) {
173 setSizeConstraint(QLayout::SetMinAndMaxSize);
174 resizer = new QWidgetResizeHandler(parent);
175 }
176 ~QDockWidgetGroupLayout() {
177 layoutState.deleteAllLayoutItems();
178 }
179
180 void addItem(QLayoutItem*) override { Q_UNREACHABLE(); }
181 int count() const override { return 0; }
182 QLayoutItem* itemAt(int index) const override
183 {
184 int x = 0;
185 return layoutState.itemAt(x: &x, index);
186 }
187 QLayoutItem* takeAt(int index) override
188 {
189 int x = 0;
190 QLayoutItem *ret = layoutState.takeAt(x: &x, index);
191 if (savedState.rect.isValid() && ret->widget()) {
192 // we need to remove the item also from the saved state to prevent crash
193 QList<int> path = savedState.indexOf(widget: ret->widget());
194 if (!path.isEmpty())
195 savedState.remove(path);
196 // Also, the item may be contained several times as a gap item.
197 path = layoutState.indexOf(widget: ret->widget());
198 if (!path.isEmpty())
199 layoutState.remove(path);
200 }
201 return ret;
202 }
203 QSize sizeHint() const override
204 {
205 int fw = frameWidth();
206 return layoutState.sizeHint() + QSize(fw, fw);
207 }
208 QSize minimumSize() const override
209 {
210 int fw = frameWidth();
211 return layoutState.minimumSize() + QSize(fw, fw);
212 }
213 QSize maximumSize() const override
214 {
215 int fw = frameWidth();
216 return layoutState.maximumSize() + QSize(fw, fw);
217 }
218 void setGeometry(const QRect&r) override
219 {
220 groupWindow()->destroyOrHideIfEmpty();
221 QDockAreaLayoutInfo *li = dockAreaLayoutInfo();
222 if (li->isEmpty())
223 return;
224 int fw = frameWidth();
225#if QT_CONFIG(tabbar)
226 li->reparentWidgets(p: parentWidget());
227#endif
228 li->rect = r.adjusted(xp1: fw, yp1: fw, xp2: -fw, yp2: -fw);
229 li->fitItems();
230 li->apply(animate: false);
231 if (savedState.rect.isValid())
232 savedState.rect = li->rect;
233 resizer->setEnabled(!nativeWindowDeco());
234 }
235
236 QDockAreaLayoutInfo *dockAreaLayoutInfo() { return &layoutState; }
237
238 bool nativeWindowDeco() const
239 {
240 return groupWindow()->hasNativeDecos();
241 }
242
243 int frameWidth() const
244 {
245 return nativeWindowDeco() ? 0 :
246 parentWidget()->style()->pixelMetric(metric: QStyle::PM_DockWidgetFrameWidth, option: nullptr, widget: parentWidget());
247 }
248
249 QDockWidgetGroupWindow *groupWindow() const
250 {
251 return static_cast<QDockWidgetGroupWindow *>(parent());
252 }
253
254 QDockAreaLayoutInfo layoutState;
255 QDockAreaLayoutInfo savedState;
256};
257
258bool QDockWidgetGroupWindow::event(QEvent *e)
259{
260 auto lay = static_cast<QDockWidgetGroupLayout *>(layout());
261 if (lay && lay->windowEvent(e))
262 return true;
263
264 switch (e->type()) {
265 case QEvent::Close:
266#if QT_CONFIG(tabbar)
267 // Forward the close to the QDockWidget just as if its close button was pressed
268 if (QDockWidget *dw = activeTabbedDockWidget()) {
269 dw->close();
270 adjustFlags();
271 }
272#endif
273 return true;
274 case QEvent::Move:
275#if QT_CONFIG(tabbar)
276 // Let QDockWidgetPrivate::moseEvent handle the dragging
277 if (QDockWidget *dw = activeTabbedDockWidget())
278 static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(o: dw))->moveEvent(event: static_cast<QMoveEvent*>(e));
279#endif
280 return true;
281 case QEvent::NonClientAreaMouseMove:
282 case QEvent::NonClientAreaMouseButtonPress:
283 case QEvent::NonClientAreaMouseButtonRelease:
284 case QEvent::NonClientAreaMouseButtonDblClick:
285#if QT_CONFIG(tabbar)
286 // Let the QDockWidgetPrivate of the currently visible dock widget handle the drag and drop
287 if (QDockWidget *dw = activeTabbedDockWidget())
288 static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(o: dw))->nonClientAreaMouseEvent(event: static_cast<QMouseEvent*>(e));
289#endif
290 return true;
291 case QEvent::ChildAdded:
292 if (qobject_cast<QDockWidget *>(object: static_cast<QChildEvent*>(e)->child()))
293 adjustFlags();
294 break;
295 case QEvent::LayoutRequest:
296 // We might need to show the widget again
297 destroyOrHideIfEmpty();
298 break;
299 case QEvent::Resize:
300 updateCurrentGapRect();
301 emit resized();
302 default:
303 break;
304 }
305 return QWidget::event(event: e);
306}
307
308void QDockWidgetGroupWindow::paintEvent(QPaintEvent *)
309{
310 QDockWidgetGroupLayout *lay = static_cast<QDockWidgetGroupLayout *>(layout());
311 bool nativeDeco = lay->nativeWindowDeco();
312
313 if (!nativeDeco) {
314 QStyleOptionFrame framOpt;
315 framOpt.initFrom(w: this);
316 QStylePainter p(this);
317 p.drawPrimitive(pe: QStyle::PE_FrameDockWidget, opt: framOpt);
318 }
319}
320
321QDockAreaLayoutInfo *QDockWidgetGroupWindow::layoutInfo() const
322{
323 return static_cast<QDockWidgetGroupLayout *>(layout())->dockAreaLayoutInfo();
324}
325
326#if QT_CONFIG(tabbar)
327/*! \internal
328 If this is a floating tab bar returns the currently the QDockWidgetGroupWindow that contains
329 tab, otherwise, return nullptr;
330 \note: if there is only one QDockWidget, it's still considered as a floating tab
331 */
332const QDockAreaLayoutInfo *QDockWidgetGroupWindow::tabLayoutInfo() const
333{
334 const QDockAreaLayoutInfo *info = layoutInfo();
335 while (info && !info->tabbed) {
336 // There should be only one tabbed subinfo otherwise we are not a floating tab but a real
337 // window
338 const QDockAreaLayoutInfo *next = nullptr;
339 bool isSingle = false;
340 for (const auto &item : info->item_list) {
341 if (item.skip() || (item.flags & QDockAreaLayoutItem::GapItem))
342 continue;
343 if (next || isSingle) // Two visible things
344 return nullptr;
345 if (item.subinfo)
346 next = item.subinfo;
347 else if (item.widgetItem)
348 isSingle = true;
349 }
350 if (isSingle)
351 return info;
352 info = next;
353 }
354 return info;
355}
356
357/*! \internal
358 If this is a floating tab bar returns the currently active QDockWidget, otherwise nullptr
359 */
360QDockWidget *QDockWidgetGroupWindow::activeTabbedDockWidget() const
361{
362 QDockWidget *dw = nullptr;
363 const QDockAreaLayoutInfo *info = tabLayoutInfo();
364 if (!info)
365 return nullptr;
366 if (info->tabBar && info->tabBar->currentIndex() >= 0) {
367 int i = info->tabIndexToListIndex(info->tabBar->currentIndex());
368 if (i >= 0) {
369 const QDockAreaLayoutItem &item = info->item_list.at(i);
370 if (item.widgetItem)
371 dw = qobject_cast<QDockWidget *>(object: item.widgetItem->widget());
372 }
373 }
374 if (!dw) {
375 for (int i = 0; !dw && i < info->item_list.size(); ++i) {
376 const QDockAreaLayoutItem &item = info->item_list.at(i);
377 if (item.skip())
378 continue;
379 if (!item.widgetItem)
380 continue;
381 dw = qobject_cast<QDockWidget *>(object: item.widgetItem->widget());
382 }
383 }
384 return dw;
385}
386#endif // QT_CONFIG(tabbar)
387
388/*! \internal
389 Destroy or hide this window if there is no more QDockWidget in it.
390 Otherwise make sure it is shown.
391 */
392void QDockWidgetGroupWindow::destroyOrHideIfEmpty()
393{
394 const QDockAreaLayoutInfo *info = layoutInfo();
395 if (!info->isEmpty()) {
396 show(); // It might have been hidden,
397 return;
398 }
399 // There might still be placeholders
400 if (!info->item_list.isEmpty()) {
401 hide();
402 return;
403 }
404
405 // Make sure to reparent the possibly floating or hidden QDockWidgets to the parent
406 const auto dockWidgets = findChildren<QDockWidget *>(options: Qt::FindDirectChildrenOnly);
407 for (QDockWidget *dw : dockWidgets) {
408 const bool wasFloating = dw->isFloating();
409 const bool wasHidden = dw->isHidden();
410 dw->setParent(parentWidget());
411 qCDebug(lcQpaDockWidgets) << "Reparented:" << dw << "to" << parentWidget() << "by" << this;
412 if (wasFloating) {
413 dw->setFloating(true);
414 } else {
415 // maybe it was hidden, we still have to put it back in the main layout.
416 QMainWindowLayout *ml =
417 qt_mainwindow_layout(window: static_cast<QMainWindow *>(parentWidget()));
418 Qt::DockWidgetArea area = ml->dockWidgetArea(widget: this);
419 if (area == Qt::NoDockWidgetArea)
420 area = Qt::LeftDockWidgetArea; // FIXME: DockWidget doesn't save original docking area
421 static_cast<QMainWindow *>(parentWidget())->addDockWidget(area, dockwidget: dw);
422 qCDebug(lcQpaDockWidgets) << "Redocked to Mainwindow:" << area << dw << "by" << this;
423 }
424 if (!wasHidden)
425 dw->show();
426 }
427#if QT_CONFIG(tabbar)
428 const auto tabBars = findChildren<QTabBar *>(options: Qt::FindDirectChildrenOnly);
429 for (QTabBar *tb : tabBars)
430 tb->setParent(parentWidget());
431#endif
432 deleteLater();
433}
434
435/*!
436 \internal
437 \return \c true if the group window has at least one visible QDockWidget child,
438 otherwise false.
439 */
440bool QDockWidgetGroupWindow::hasVisibleDockWidgets() const
441{
442 const auto &children = findChildren<QDockWidget *>(options: Qt::FindChildrenRecursively);
443 for (auto child : children) {
444 // WA_WState_Visible is set on the dock widget, associated to the active tab
445 // and unset on all others.
446 // WA_WState_Hidden is set if the dock widgets have been explicitly hidden.
447 // This is the relevant information to check (equivalent to !child->isHidden()).
448 if (!child->testAttribute(attribute: Qt::WA_WState_Hidden))
449 return true;
450 }
451 return false;
452}
453
454/*! \internal
455 Sets the flags of this window in accordance to the capabilities of the dock widgets
456 */
457void QDockWidgetGroupWindow::adjustFlags()
458{
459 Qt::WindowFlags oldFlags = windowFlags();
460 Qt::WindowFlags flags = oldFlags;
461
462#if QT_CONFIG(tabbar)
463 QDockWidget *top = activeTabbedDockWidget();
464#else
465 QDockWidget *top = nullptr;
466#endif
467 if (!top) { // nested tabs, show window decoration
468 flags =
469 ((oldFlags & ~Qt::FramelessWindowHint) | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
470 } else if (static_cast<QDockWidgetGroupLayout *>(layout())->nativeWindowDeco()) {
471 flags |= Qt::CustomizeWindowHint | Qt::WindowTitleHint;
472 flags.setFlag(flag: Qt::WindowCloseButtonHint, on: top->features() & QDockWidget::DockWidgetClosable);
473 flags &= ~Qt::FramelessWindowHint;
474 } else {
475 flags &= ~(Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
476 flags |= Qt::FramelessWindowHint;
477 }
478
479 if (oldFlags != flags) {
480 if (!windowHandle())
481 create(); // The desired geometry is forgotten if we call setWindowFlags before having a window
482 setWindowFlags(flags);
483 const bool gainedNativeDecos = (oldFlags & Qt::FramelessWindowHint) && !(flags & Qt::FramelessWindowHint);
484 const bool lostNativeDecos = !(oldFlags & Qt::FramelessWindowHint) && (flags & Qt::FramelessWindowHint);
485
486 // Adjust the geometry after gaining/losing decos, so that the client area appears always
487 // at the same place when tabbing
488 if (lostNativeDecos) {
489 QRect newGeometry = geometry();
490 newGeometry.setTop(frameGeometry().top());
491 const int bottomFrame = geometry().top() - frameGeometry().top();
492 m_removedFrameSize = QSize((frameSize() - size()).width(), bottomFrame);
493 setGeometry(newGeometry);
494 } else if (gainedNativeDecos && m_removedFrameSize.isValid()) {
495 QRect r = geometry();
496 r.adjust(dx1: -m_removedFrameSize.width() / 2, dy1: 0,
497 dx2: -m_removedFrameSize.width() / 2, dy2: -m_removedFrameSize.height());
498 setGeometry(r);
499 m_removedFrameSize = QSize();
500 }
501
502 setVisible(hasVisibleDockWidgets());
503 }
504
505 QWidget *titleBarOf = top ? top : parentWidget();
506 setWindowTitle(titleBarOf->windowTitle());
507 setWindowIcon(titleBarOf->windowIcon());
508}
509
510bool QDockWidgetGroupWindow::hasNativeDecos() const
511{
512#if QT_CONFIG(tabbar)
513 QDockWidget *dw = activeTabbedDockWidget();
514 if (!dw) // We have a group of nested QDockWidgets (not just floating tabs)
515 return true;
516
517 if (!QDockWidgetLayout::wmSupportsNativeWindowDeco())
518 return false;
519
520 return dw->titleBarWidget() == nullptr;
521#else
522 return true;
523#endif
524}
525
526/*
527 The given widget is hovered over this floating group.
528 This function will save the state and create a gap in the actual state.
529 currentGapRect and currentGapPos will be set.
530 One must call restore() or apply() after this function.
531 Returns true if there was any change in the currentGapPos
532 */
533bool QDockWidgetGroupWindow::hover(QLayoutItem *widgetItem, const QPoint &mousePos)
534{
535 QDockAreaLayoutInfo &savedState = static_cast<QDockWidgetGroupLayout *>(layout())->savedState;
536 if (savedState.isEmpty())
537 savedState = *layoutInfo();
538
539 QMainWindow::DockOptions opts = static_cast<QMainWindow *>(parentWidget())->dockOptions();
540 QDockAreaLayoutInfo newState = savedState;
541 bool nestingEnabled =
542 (opts & QMainWindow::AllowNestedDocks) && !(opts & QMainWindow::ForceTabbedDocks);
543 QDockAreaLayoutInfo::TabMode tabMode =
544#if !QT_CONFIG(tabbar)
545 QDockAreaLayoutInfo::NoTabs;
546#else
547 nestingEnabled ? QDockAreaLayoutInfo::AllowTabs : QDockAreaLayoutInfo::ForceTabs;
548 if (auto group = qobject_cast<QDockWidgetGroupWindow *>(object: widgetItem->widget())) {
549 if (!group->tabLayoutInfo())
550 tabMode = QDockAreaLayoutInfo::NoTabs;
551 }
552 if (newState.tabbed) {
553 // insertion into a top-level tab
554 newState.item_list = { QDockAreaLayoutItem(new QDockAreaLayoutInfo(newState)) };
555 newState.item_list.first().size = pick(o: savedState.o, size: savedState.rect.size());
556 newState.tabbed = false;
557 newState.tabBar = nullptr;
558 }
559#endif
560
561 auto newGapPos = newState.gapIndex(pos: mousePos, nestingEnabled, tabMode);
562 Q_ASSERT(!newGapPos.isEmpty());
563
564 // Do not insert a new gap item, if the current position already is a gap,
565 // or if the group window contains one
566 if (newGapPos == currentGapPos || newState.hasGapItem(path: newGapPos))
567 return false;
568
569 currentGapPos = newGapPos;
570 newState.insertGap(path: currentGapPos, dockWidgetItem: widgetItem);
571 newState.fitItems();
572 *layoutInfo() = std::move(newState);
573 updateCurrentGapRect();
574 layoutInfo()->apply(animate: opts & QMainWindow::AnimatedDocks);
575 return true;
576}
577
578void QDockWidgetGroupWindow::updateCurrentGapRect()
579{
580 if (!currentGapPos.isEmpty())
581 currentGapRect = layoutInfo()->info(path: currentGapPos)->itemRect(index: currentGapPos.last(), isGap: true);
582}
583
584/*
585 Remove the gap that was created by hover()
586 */
587void QDockWidgetGroupWindow::restore()
588{
589 QDockAreaLayoutInfo &savedState = static_cast<QDockWidgetGroupLayout *>(layout())->savedState;
590 if (!savedState.isEmpty()) {
591 *layoutInfo() = savedState;
592 savedState = QDockAreaLayoutInfo();
593 }
594 currentGapRect = QRect();
595 currentGapPos.clear();
596 adjustFlags();
597 layoutInfo()->fitItems();
598 layoutInfo()->apply(animate: static_cast<QMainWindow *>(parentWidget())->dockOptions()
599 & QMainWindow::AnimatedDocks);
600}
601
602/*
603 Apply the state that was created by hover
604 */
605void QDockWidgetGroupWindow::apply()
606{
607 static_cast<QDockWidgetGroupLayout *>(layout())->savedState.clear();
608 currentGapRect = QRect();
609 layoutInfo()->plug(path: currentGapPos);
610 currentGapPos.clear();
611 adjustFlags();
612 layoutInfo()->apply(animate: false);
613}
614
615#endif
616
617/******************************************************************************
618** QMainWindowLayoutState
619*/
620
621// we deal with all the #ifndefferry here so QMainWindowLayout code is clean
622
623QMainWindowLayoutState::QMainWindowLayoutState(QMainWindow *win)
624 :
625#if QT_CONFIG(toolbar)
626 toolBarAreaLayout(win),
627#endif
628#if QT_CONFIG(dockwidget)
629 dockAreaLayout(win)
630#else
631 centralWidgetItem(0)
632#endif
633
634{
635 mainWindow = win;
636}
637
638QSize QMainWindowLayoutState::sizeHint() const
639{
640
641 QSize result(0, 0);
642
643#if QT_CONFIG(dockwidget)
644 result = dockAreaLayout.sizeHint();
645#else
646 if (centralWidgetItem)
647 result = centralWidgetItem->sizeHint();
648#endif
649
650#if QT_CONFIG(toolbar)
651 result = toolBarAreaLayout.sizeHint(center: result);
652#endif // QT_CONFIG(toolbar)
653
654 return result;
655}
656
657QSize QMainWindowLayoutState::minimumSize() const
658{
659 QSize result(0, 0);
660
661#if QT_CONFIG(dockwidget)
662 result = dockAreaLayout.minimumSize();
663#else
664 if (centralWidgetItem)
665 result = centralWidgetItem->minimumSize();
666#endif
667
668#if QT_CONFIG(toolbar)
669 result = toolBarAreaLayout.minimumSize(centerMin: result);
670#endif // QT_CONFIG(toolbar)
671
672 return result;
673}
674
675/*!
676 \internal
677
678 Returns whether the layout fits into the main window.
679*/
680bool QMainWindowLayoutState::fits() const
681{
682 Q_ASSERT(mainWindow);
683
684 QSize size;
685
686#if QT_CONFIG(dockwidget)
687 size = dockAreaLayout.minimumStableSize();
688#endif
689
690#if QT_CONFIG(toolbar)
691 size.rwidth() += toolBarAreaLayout.docks[QInternal::LeftDock].rect.width();
692 size.rwidth() += toolBarAreaLayout.docks[QInternal::RightDock].rect.width();
693 size.rheight() += toolBarAreaLayout.docks[QInternal::TopDock].rect.height();
694 size.rheight() += toolBarAreaLayout.docks[QInternal::BottomDock].rect.height();
695#endif
696
697 return size.width() <= mainWindow->width() && size.height() <= mainWindow->height();
698}
699
700void QMainWindowLayoutState::apply(bool animated)
701{
702#if QT_CONFIG(toolbar)
703 toolBarAreaLayout.apply(animate: animated);
704#endif
705
706#if QT_CONFIG(dockwidget)
707// dumpLayout(dockAreaLayout, QString());
708 dockAreaLayout.apply(animate: animated);
709#else
710 if (centralWidgetItem) {
711 QMainWindowLayout *layout = qt_mainwindow_layout(mainWindow);
712 Q_ASSERT(layout);
713 layout->widgetAnimator.animate(centralWidgetItem->widget(), centralWidgetRect, animated);
714 }
715#endif
716}
717
718void QMainWindowLayoutState::fitLayout()
719{
720 QRect r;
721#if !QT_CONFIG(toolbar)
722 r = rect;
723#else
724 toolBarAreaLayout.rect = rect;
725 r = toolBarAreaLayout.fitLayout();
726#endif // QT_CONFIG(toolbar)
727
728#if QT_CONFIG(dockwidget)
729 dockAreaLayout.rect = r;
730 dockAreaLayout.fitLayout();
731#else
732 centralWidgetRect = r;
733#endif
734}
735
736void QMainWindowLayoutState::deleteAllLayoutItems()
737{
738#if QT_CONFIG(toolbar)
739 toolBarAreaLayout.deleteAllLayoutItems();
740#endif
741
742#if QT_CONFIG(dockwidget)
743 dockAreaLayout.deleteAllLayoutItems();
744#endif
745}
746
747void QMainWindowLayoutState::deleteCentralWidgetItem()
748{
749#if QT_CONFIG(dockwidget)
750 delete dockAreaLayout.centralWidgetItem;
751 dockAreaLayout.centralWidgetItem = nullptr;
752#else
753 delete centralWidgetItem;
754 centralWidgetItem = 0;
755#endif
756}
757
758QLayoutItem *QMainWindowLayoutState::itemAt(int index, int *x) const
759{
760#if QT_CONFIG(toolbar)
761 if (QLayoutItem *ret = toolBarAreaLayout.itemAt(x, index))
762 return ret;
763#endif
764
765#if QT_CONFIG(dockwidget)
766 if (QLayoutItem *ret = dockAreaLayout.itemAt(x, index))
767 return ret;
768#else
769 if (centralWidgetItem && (*x)++ == index)
770 return centralWidgetItem;
771#endif
772
773 return nullptr;
774}
775
776QLayoutItem *QMainWindowLayoutState::takeAt(int index, int *x)
777{
778#if QT_CONFIG(toolbar)
779 if (QLayoutItem *ret = toolBarAreaLayout.takeAt(x, index))
780 return ret;
781#endif
782
783#if QT_CONFIG(dockwidget)
784 if (QLayoutItem *ret = dockAreaLayout.takeAt(x, index))
785 return ret;
786#else
787 if (centralWidgetItem && (*x)++ == index) {
788 QLayoutItem *ret = centralWidgetItem;
789 centralWidgetItem = nullptr;
790 return ret;
791 }
792#endif
793
794 return nullptr;
795}
796
797QList<int> QMainWindowLayoutState::indexOf(QWidget *widget) const
798{
799 QList<int> result;
800
801#if QT_CONFIG(toolbar)
802 // is it a toolbar?
803 if (QToolBar *toolBar = qobject_cast<QToolBar*>(object: widget)) {
804 result = toolBarAreaLayout.indexOf(toolBar);
805 if (!result.isEmpty())
806 result.prepend(t: 0);
807 return result;
808 }
809#endif
810
811#if QT_CONFIG(dockwidget)
812 // is it a dock widget?
813 if (qobject_cast<QDockWidget *>(object: widget) || qobject_cast<QDockWidgetGroupWindow *>(object: widget)) {
814 result = dockAreaLayout.indexOf(dockWidget: widget);
815 if (!result.isEmpty())
816 result.prepend(t: 1);
817 return result;
818 }
819#endif // QT_CONFIG(dockwidget)
820
821 return result;
822}
823
824bool QMainWindowLayoutState::contains(QWidget *widget) const
825{
826#if QT_CONFIG(dockwidget)
827 if (dockAreaLayout.centralWidgetItem != nullptr && dockAreaLayout.centralWidgetItem->widget() == widget)
828 return true;
829 if (!dockAreaLayout.indexOf(dockWidget: widget).isEmpty())
830 return true;
831#else
832 if (centralWidgetItem && centralWidgetItem->widget() == widget)
833 return true;
834#endif
835
836#if QT_CONFIG(toolbar)
837 if (!toolBarAreaLayout.indexOf(toolBar: widget).isEmpty())
838 return true;
839#endif
840 return false;
841}
842
843void QMainWindowLayoutState::setCentralWidget(QWidget *widget)
844{
845 QLayoutItem *item = nullptr;
846 //make sure we remove the widget
847 deleteCentralWidgetItem();
848
849 if (widget != nullptr)
850 item = new QWidgetItemV2(widget);
851
852#if QT_CONFIG(dockwidget)
853 dockAreaLayout.centralWidgetItem = item;
854#else
855 centralWidgetItem = item;
856#endif
857}
858
859QWidget *QMainWindowLayoutState::centralWidget() const
860{
861 QLayoutItem *item = nullptr;
862
863#if QT_CONFIG(dockwidget)
864 item = dockAreaLayout.centralWidgetItem;
865#else
866 item = centralWidgetItem;
867#endif
868
869 if (item != nullptr)
870 return item->widget();
871 return nullptr;
872}
873
874QList<int> QMainWindowLayoutState::gapIndex(QWidget *widget,
875 const QPoint &pos) const
876{
877 QList<int> result;
878
879#if QT_CONFIG(toolbar)
880 // is it a toolbar?
881 if (qobject_cast<QToolBar*>(object: widget) != nullptr) {
882 result = toolBarAreaLayout.gapIndex(pos);
883 if (!result.isEmpty())
884 result.prepend(t: 0);
885 return result;
886 }
887#endif
888
889#if QT_CONFIG(dockwidget)
890 // is it a dock widget?
891 if (qobject_cast<QDockWidget *>(object: widget) != nullptr
892 || qobject_cast<QDockWidgetGroupWindow *>(object: widget)) {
893 bool disallowTabs = false;
894#if QT_CONFIG(tabbar)
895 if (auto *group = qobject_cast<QDockWidgetGroupWindow *>(object: widget)) {
896 if (!group->tabLayoutInfo()) // Disallow to drop nested docks as a tab
897 disallowTabs = true;
898 }
899#endif
900 result = dockAreaLayout.gapIndex(pos, disallowTabs);
901 if (!result.isEmpty())
902 result.prepend(t: 1);
903 return result;
904 }
905#endif // QT_CONFIG(dockwidget)
906
907 return result;
908}
909
910bool QMainWindowLayoutState::insertGap(const QList<int> &path, QLayoutItem *item)
911{
912 if (path.isEmpty())
913 return false;
914
915 int i = path.first();
916
917#if QT_CONFIG(toolbar)
918 if (i == 0) {
919 Q_ASSERT(qobject_cast<QToolBar*>(item->widget()) != nullptr);
920 return toolBarAreaLayout.insertGap(path: path.mid(pos: 1), item);
921 }
922#endif
923
924#if QT_CONFIG(dockwidget)
925 if (i == 1) {
926 Q_ASSERT(qobject_cast<QDockWidget*>(item->widget()) || qobject_cast<QDockWidgetGroupWindow*>(item->widget()));
927 return dockAreaLayout.insertGap(path: path.mid(pos: 1), dockWidgetItem: item);
928 }
929#endif // QT_CONFIG(dockwidget)
930
931 return false;
932}
933
934void QMainWindowLayoutState::remove(const QList<int> &path)
935{
936 int i = path.first();
937
938#if QT_CONFIG(toolbar)
939 if (i == 0)
940 toolBarAreaLayout.remove(path: path.mid(pos: 1));
941#endif
942
943#if QT_CONFIG(dockwidget)
944 if (i == 1)
945 dockAreaLayout.remove(path: path.mid(pos: 1));
946#endif // QT_CONFIG(dockwidget)
947}
948
949void QMainWindowLayoutState::remove(QLayoutItem *item)
950{
951#if QT_CONFIG(toolbar)
952 toolBarAreaLayout.remove(item);
953#endif
954
955#if QT_CONFIG(dockwidget)
956 // is it a dock widget?
957 if (QDockWidget *dockWidget = qobject_cast<QDockWidget *>(object: item->widget())) {
958 QList<int> path = dockAreaLayout.indexOf(dockWidget);
959 if (!path.isEmpty())
960 dockAreaLayout.remove(path);
961 }
962#endif // QT_CONFIG(dockwidget)
963}
964
965void QMainWindowLayoutState::clear()
966{
967#if QT_CONFIG(toolbar)
968 toolBarAreaLayout.clear();
969#endif
970
971#if QT_CONFIG(dockwidget)
972 dockAreaLayout.clear();
973#else
974 centralWidgetRect = QRect();
975#endif
976
977 rect = QRect();
978}
979
980bool QMainWindowLayoutState::isValid() const
981{
982 return rect.isValid();
983}
984
985QLayoutItem *QMainWindowLayoutState::item(const QList<int> &path)
986{
987 int i = path.first();
988
989#if QT_CONFIG(toolbar)
990 if (i == 0) {
991 const QToolBarAreaLayoutItem *tbItem = toolBarAreaLayout.item(path: path.mid(pos: 1));
992 Q_ASSERT(tbItem);
993 return tbItem->widgetItem;
994 }
995#endif
996
997#if QT_CONFIG(dockwidget)
998 if (i == 1)
999 return dockAreaLayout.item(path: path.mid(pos: 1)).widgetItem;
1000#endif // QT_CONFIG(dockwidget)
1001
1002 return nullptr;
1003}
1004
1005QRect QMainWindowLayoutState::itemRect(const QList<int> &path) const
1006{
1007 int i = path.first();
1008
1009#if QT_CONFIG(toolbar)
1010 if (i == 0)
1011 return toolBarAreaLayout.itemRect(path: path.mid(pos: 1));
1012#endif
1013
1014#if QT_CONFIG(dockwidget)
1015 if (i == 1)
1016 return dockAreaLayout.itemRect(path: path.mid(pos: 1));
1017#endif // QT_CONFIG(dockwidget)
1018
1019 return QRect();
1020}
1021
1022QRect QMainWindowLayoutState::gapRect(const QList<int> &path) const
1023{
1024 int i = path.first();
1025
1026#if QT_CONFIG(toolbar)
1027 if (i == 0)
1028 return toolBarAreaLayout.itemRect(path: path.mid(pos: 1));
1029#endif
1030
1031#if QT_CONFIG(dockwidget)
1032 if (i == 1)
1033 return dockAreaLayout.gapRect(path: path.mid(pos: 1));
1034#endif // QT_CONFIG(dockwidget)
1035
1036 return QRect();
1037}
1038
1039QLayoutItem *QMainWindowLayoutState::plug(const QList<int> &path)
1040{
1041 int i = path.first();
1042
1043#if QT_CONFIG(toolbar)
1044 if (i == 0)
1045 return toolBarAreaLayout.plug(path: path.mid(pos: 1));
1046#endif
1047
1048#if QT_CONFIG(dockwidget)
1049 if (i == 1)
1050 return dockAreaLayout.plug(path: path.mid(pos: 1));
1051#endif // QT_CONFIG(dockwidget)
1052
1053 return nullptr;
1054}
1055
1056QLayoutItem *QMainWindowLayoutState::unplug(const QList<int> &path, QMainWindowLayoutState *other)
1057{
1058 int i = path.first();
1059
1060#if !QT_CONFIG(toolbar)
1061 Q_UNUSED(other);
1062#else
1063 if (i == 0)
1064 return toolBarAreaLayout.unplug(path: path.mid(pos: 1), other: other ? &other->toolBarAreaLayout : nullptr);
1065#endif
1066
1067#if QT_CONFIG(dockwidget)
1068 if (i == 1)
1069 return dockAreaLayout.unplug(path: path.mid(pos: 1));
1070#endif // QT_CONFIG(dockwidget)
1071
1072 return nullptr;
1073}
1074
1075void QMainWindowLayoutState::saveState(QDataStream &stream) const
1076{
1077#if QT_CONFIG(dockwidget)
1078 dockAreaLayout.saveState(stream);
1079#if QT_CONFIG(tabbar)
1080 const QList<QDockWidgetGroupWindow *> floatingTabs =
1081 mainWindow->findChildren<QDockWidgetGroupWindow *>(options: Qt::FindDirectChildrenOnly);
1082
1083 for (QDockWidgetGroupWindow *floating : floatingTabs) {
1084 if (floating->layoutInfo()->isEmpty())
1085 continue;
1086 stream << uchar(QDockAreaLayout::FloatingDockWidgetTabMarker) << floating->geometry();
1087 floating->layoutInfo()->saveState(stream);
1088 }
1089#endif
1090#endif
1091#if QT_CONFIG(toolbar)
1092 toolBarAreaLayout.saveState(stream);
1093#endif
1094}
1095
1096template <typename T>
1097static QList<T> findChildrenHelper(const QObject *o)
1098{
1099 const QObjectList &list = o->children();
1100 QList<T> result;
1101
1102 for (int i=0; i < list.size(); ++i) {
1103 if (T t = qobject_cast<T>(list[i])) {
1104 result.append(t);
1105 }
1106 }
1107
1108 return result;
1109}
1110
1111#if QT_CONFIG(dockwidget)
1112static QList<QDockWidget*> allMyDockWidgets(const QWidget *mainWindow)
1113{
1114 QList<QDockWidget*> result;
1115 for (QObject *c : mainWindow->children()) {
1116 if (auto *dw = qobject_cast<QDockWidget*>(object: c)) {
1117 result.append(t: dw);
1118 } else if (auto *gw = qobject_cast<QDockWidgetGroupWindow*>(object: c)) {
1119 for (QObject *c : gw->children()) {
1120 if (auto *dw = qobject_cast<QDockWidget*>(object: c))
1121 result.append(t: dw);
1122 }
1123 }
1124 }
1125
1126 return result;
1127}
1128#endif // QT_CONFIG(dockwidget)
1129
1130//pre4.3 tests the format that was used before 4.3
1131bool QMainWindowLayoutState::checkFormat(QDataStream &stream)
1132{
1133 while (!stream.atEnd()) {
1134 uchar marker;
1135 stream >> marker;
1136 switch(marker)
1137 {
1138#if QT_CONFIG(toolbar)
1139 case QToolBarAreaLayout::ToolBarStateMarker:
1140 case QToolBarAreaLayout::ToolBarStateMarkerEx:
1141 {
1142 QList<QToolBar *> toolBars = findChildrenHelper<QToolBar*>(o: mainWindow);
1143 if (!toolBarAreaLayout.restoreState(stream, toolBars, tmarker: marker, testing: true /*testing*/)) {
1144 return false;
1145 }
1146 }
1147 break;
1148#endif // QT_CONFIG(toolbar)
1149
1150#if QT_CONFIG(dockwidget)
1151 case QDockAreaLayout::DockWidgetStateMarker:
1152 {
1153 const auto dockWidgets = allMyDockWidgets(mainWindow);
1154 if (!dockAreaLayout.restoreState(stream, widgets: dockWidgets, testing: true /*testing*/)) {
1155 return false;
1156 }
1157 }
1158 break;
1159#if QT_CONFIG(tabbar)
1160 case QDockAreaLayout::FloatingDockWidgetTabMarker:
1161 {
1162 QRect geom;
1163 stream >> geom;
1164 QDockAreaLayoutInfo info;
1165 auto dockWidgets = allMyDockWidgets(mainWindow);
1166 if (!info.restoreState(stream, widgets&: dockWidgets, testing: true /* testing*/))
1167 return false;
1168 }
1169 break;
1170#endif // QT_CONFIG(tabbar)
1171#endif // QT_CONFIG(dockwidget)
1172 default:
1173 //there was an error during the parsing
1174 return false;
1175 }// switch
1176 } //while
1177
1178 //everything went fine: it must be a pre-4.3 saved state
1179 return true;
1180}
1181
1182bool QMainWindowLayoutState::restoreState(QDataStream &_stream,
1183 const QMainWindowLayoutState &oldState)
1184{
1185 //make a copy of the data so that we can read it more than once
1186 QByteArray copy;
1187 while(!_stream.atEnd()) {
1188 int length = 1024;
1189 QByteArray ba(length, '\0');
1190 length = _stream.readRawData(ba.data(), len: ba.size());
1191 ba.resize(size: length);
1192 copy += ba;
1193 }
1194
1195 QDataStream ds(copy);
1196 ds.setVersion(_stream.version());
1197 if (!checkFormat(stream&: ds))
1198 return false;
1199
1200 QDataStream stream(copy);
1201 stream.setVersion(_stream.version());
1202
1203 while (!stream.atEnd()) {
1204 uchar marker;
1205 stream >> marker;
1206 switch(marker)
1207 {
1208#if QT_CONFIG(dockwidget)
1209 case QDockAreaLayout::DockWidgetStateMarker:
1210 {
1211 const auto dockWidgets = allMyDockWidgets(mainWindow);
1212 if (!dockAreaLayout.restoreState(stream, widgets: dockWidgets))
1213 return false;
1214
1215 for (int i = 0; i < dockWidgets.size(); ++i) {
1216 QDockWidget *w = dockWidgets.at(i);
1217 QList<int> path = dockAreaLayout.indexOf(dockWidget: w);
1218 if (path.isEmpty()) {
1219 QList<int> oldPath = oldState.dockAreaLayout.indexOf(dockWidget: w);
1220 if (oldPath.isEmpty()) {
1221 continue;
1222 }
1223 QDockAreaLayoutInfo *info = dockAreaLayout.info(path: oldPath);
1224 if (info == nullptr) {
1225 continue;
1226 }
1227 info->item_list.append(t: QDockAreaLayoutItem(new QDockWidgetItem(w)));
1228 }
1229 }
1230 }
1231 break;
1232#if QT_CONFIG(tabwidget)
1233 case QDockAreaLayout::FloatingDockWidgetTabMarker:
1234 {
1235 auto dockWidgets = allMyDockWidgets(mainWindow);
1236 QDockWidgetGroupWindow* floatingTab = qt_mainwindow_layout(window: mainWindow)->createTabbedDockWindow();
1237 *floatingTab->layoutInfo() = QDockAreaLayoutInfo(
1238 &dockAreaLayout.sep, QInternal::LeftDock, // FIXME: DockWidget doesn't save original docking area
1239 Qt::Horizontal, QTabBar::RoundedSouth, mainWindow);
1240 QRect geometry;
1241 stream >> geometry;
1242 QDockAreaLayoutInfo *info = floatingTab->layoutInfo();
1243 if (!info->restoreState(stream, widgets&: dockWidgets, testing: false))
1244 return false;
1245 geometry = QDockAreaLayout::constrainedRect(rect: geometry, widget: floatingTab);
1246 floatingTab->move(geometry.topLeft());
1247 floatingTab->resize(geometry.size());
1248
1249 // Don't show an empty QDockWidgetGroupWindow if no dock widget is available yet.
1250 // reparentWidgets() would be triggered by show(), so do it explicitly here.
1251 if (info->onlyHasPlaceholders())
1252 info->reparentWidgets(p: floatingTab);
1253 else
1254 floatingTab->show();
1255 }
1256 break;
1257#endif // QT_CONFIG(tabwidget)
1258#endif // QT_CONFIG(dockwidget)
1259
1260#if QT_CONFIG(toolbar)
1261 case QToolBarAreaLayout::ToolBarStateMarker:
1262 case QToolBarAreaLayout::ToolBarStateMarkerEx:
1263 {
1264 QList<QToolBar *> toolBars = findChildrenHelper<QToolBar*>(o: mainWindow);
1265 if (!toolBarAreaLayout.restoreState(stream, toolBars, tmarker: marker))
1266 return false;
1267
1268 for (int i = 0; i < toolBars.size(); ++i) {
1269 QToolBar *w = toolBars.at(i);
1270 QList<int> path = toolBarAreaLayout.indexOf(toolBar: w);
1271 if (path.isEmpty()) {
1272 QList<int> oldPath = oldState.toolBarAreaLayout.indexOf(toolBar: w);
1273 if (oldPath.isEmpty()) {
1274 continue;
1275 }
1276 toolBarAreaLayout.docks[oldPath.at(i: 0)].insertToolBar(before: nullptr, toolBar: w);
1277 }
1278 }
1279 }
1280 break;
1281#endif // QT_CONFIG(toolbar)
1282 default:
1283 return false;
1284 }// switch
1285 } //while
1286
1287
1288 return true;
1289}
1290
1291/******************************************************************************
1292** QMainWindowLayoutState - toolbars
1293*/
1294
1295#if QT_CONFIG(toolbar)
1296
1297static inline void validateToolBarArea(Qt::ToolBarArea &area)
1298{
1299 switch (area) {
1300 case Qt::LeftToolBarArea:
1301 case Qt::RightToolBarArea:
1302 case Qt::TopToolBarArea:
1303 case Qt::BottomToolBarArea:
1304 break;
1305 default:
1306 area = Qt::TopToolBarArea;
1307 }
1308}
1309
1310static QInternal::DockPosition toDockPos(Qt::ToolBarArea area)
1311{
1312 switch (area) {
1313 case Qt::LeftToolBarArea: return QInternal::LeftDock;
1314 case Qt::RightToolBarArea: return QInternal::RightDock;
1315 case Qt::TopToolBarArea: return QInternal::TopDock;
1316 case Qt::BottomToolBarArea: return QInternal::BottomDock;
1317 default:
1318 break;
1319 }
1320
1321 return QInternal::DockCount;
1322}
1323
1324static Qt::ToolBarArea toToolBarArea(QInternal::DockPosition pos)
1325{
1326 switch (pos) {
1327 case QInternal::LeftDock: return Qt::LeftToolBarArea;
1328 case QInternal::RightDock: return Qt::RightToolBarArea;
1329 case QInternal::TopDock: return Qt::TopToolBarArea;
1330 case QInternal::BottomDock: return Qt::BottomToolBarArea;
1331 default: break;
1332 }
1333 return Qt::NoToolBarArea;
1334}
1335
1336static inline Qt::ToolBarArea toToolBarArea(int pos)
1337{
1338 return toToolBarArea(pos: static_cast<QInternal::DockPosition>(pos));
1339}
1340
1341void QMainWindowLayout::addToolBarBreak(Qt::ToolBarArea area)
1342{
1343 validateToolBarArea(area);
1344
1345 layoutState.toolBarAreaLayout.addToolBarBreak(pos: toDockPos(area));
1346 if (savedState.isValid())
1347 savedState.toolBarAreaLayout.addToolBarBreak(pos: toDockPos(area));
1348
1349 invalidate();
1350}
1351
1352void QMainWindowLayout::insertToolBarBreak(QToolBar *before)
1353{
1354 layoutState.toolBarAreaLayout.insertToolBarBreak(before);
1355 if (savedState.isValid())
1356 savedState.toolBarAreaLayout.insertToolBarBreak(before);
1357 invalidate();
1358}
1359
1360void QMainWindowLayout::removeToolBarBreak(QToolBar *before)
1361{
1362 layoutState.toolBarAreaLayout.removeToolBarBreak(before);
1363 if (savedState.isValid())
1364 savedState.toolBarAreaLayout.removeToolBarBreak(before);
1365 invalidate();
1366}
1367
1368void QMainWindowLayout::moveToolBar(QToolBar *toolbar, int pos)
1369{
1370 layoutState.toolBarAreaLayout.moveToolBar(toolbar, pos);
1371 if (savedState.isValid())
1372 savedState.toolBarAreaLayout.moveToolBar(toolbar, pos);
1373 invalidate();
1374}
1375
1376/* Removes the toolbar from the mainwindow so that it can be added again. Does not
1377 explicitly hide the toolbar. */
1378void QMainWindowLayout::removeToolBar(QToolBar *toolbar)
1379{
1380 if (toolbar) {
1381 QObject::disconnect(sender: parentWidget(), SIGNAL(iconSizeChanged(QSize)),
1382 receiver: toolbar, SLOT(_q_updateIconSize(QSize)));
1383 QObject::disconnect(sender: parentWidget(), SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)),
1384 receiver: toolbar, SLOT(_q_updateToolButtonStyle(Qt::ToolButtonStyle)));
1385
1386 removeWidget(w: toolbar);
1387 }
1388}
1389
1390/*!
1391 Adds \a toolbar to \a area, continuing the current line.
1392*/
1393void QMainWindowLayout::addToolBar(Qt::ToolBarArea area,
1394 QToolBar *toolbar,
1395 bool)
1396{
1397 validateToolBarArea(area);
1398 // let's add the toolbar to the layout
1399 addChildWidget(w: toolbar);
1400 QLayoutItem *item = layoutState.toolBarAreaLayout.addToolBar(pos: toDockPos(area), toolBar: toolbar);
1401 if (savedState.isValid() && item) {
1402 // copy the toolbar also in the saved state
1403 savedState.toolBarAreaLayout.insertItem(pos: toDockPos(area), item);
1404 }
1405 invalidate();
1406
1407 // this ensures that the toolbar has the right window flags (not floating any more)
1408 toolbar->d_func()->updateWindowFlags(floating: false /*floating*/);
1409}
1410
1411/*!
1412 Adds \a toolbar before \a before
1413*/
1414void QMainWindowLayout::insertToolBar(QToolBar *before, QToolBar *toolbar)
1415{
1416 addChildWidget(w: toolbar);
1417 QLayoutItem *item = layoutState.toolBarAreaLayout.insertToolBar(before, toolBar: toolbar);
1418 if (savedState.isValid() && item) {
1419 // copy the toolbar also in the saved state
1420 savedState.toolBarAreaLayout.insertItem(before, item);
1421 }
1422 if (!currentGapPos.isEmpty() && currentGapPos.constFirst() == 0) {
1423 currentGapPos = layoutState.toolBarAreaLayout.currentGapIndex();
1424 if (!currentGapPos.isEmpty()) {
1425 currentGapPos.prepend(t: 0);
1426 currentGapRect = layoutState.itemRect(path: currentGapPos);
1427 }
1428 }
1429 invalidate();
1430}
1431
1432Qt::ToolBarArea QMainWindowLayout::toolBarArea(const QToolBar *toolbar) const
1433{
1434 QInternal::DockPosition pos = layoutState.toolBarAreaLayout.findToolBar(toolBar: toolbar);
1435 switch (pos) {
1436 case QInternal::LeftDock: return Qt::LeftToolBarArea;
1437 case QInternal::RightDock: return Qt::RightToolBarArea;
1438 case QInternal::TopDock: return Qt::TopToolBarArea;
1439 case QInternal::BottomDock: return Qt::BottomToolBarArea;
1440 default: break;
1441 }
1442 return Qt::NoToolBarArea;
1443}
1444
1445bool QMainWindowLayout::toolBarBreak(QToolBar *toolBar) const
1446{
1447 return layoutState.toolBarAreaLayout.toolBarBreak(toolBar);
1448}
1449
1450void QMainWindowLayout::getStyleOptionInfo(QStyleOptionToolBar *option, QToolBar *toolBar) const
1451{
1452 option->toolBarArea = toolBarArea(toolbar: toolBar);
1453 layoutState.toolBarAreaLayout.getStyleOptionInfo(option, toolBar);
1454}
1455
1456void QMainWindowLayout::toggleToolBarsVisible()
1457{
1458 layoutState.toolBarAreaLayout.visible = !layoutState.toolBarAreaLayout.visible;
1459 if (!layoutState.mainWindow->isMaximized()) {
1460 QPoint topLeft = parentWidget()->geometry().topLeft();
1461 QRect r = parentWidget()->geometry();
1462 r = layoutState.toolBarAreaLayout.rectHint(r);
1463 r.moveTo(p: topLeft);
1464 parentWidget()->setGeometry(r);
1465 } else {
1466 update();
1467 }
1468}
1469
1470#endif // QT_CONFIG(toolbar)
1471
1472/******************************************************************************
1473** QMainWindowLayoutState - dock areas
1474*/
1475
1476#if QT_CONFIG(dockwidget)
1477
1478static QInternal::DockPosition toDockPos(Qt::DockWidgetArea area)
1479{
1480 switch (area) {
1481 case Qt::LeftDockWidgetArea: return QInternal::LeftDock;
1482 case Qt::RightDockWidgetArea: return QInternal::RightDock;
1483 case Qt::TopDockWidgetArea: return QInternal::TopDock;
1484 case Qt::BottomDockWidgetArea: return QInternal::BottomDock;
1485 default:
1486 break;
1487 }
1488
1489 return QInternal::DockCount;
1490}
1491
1492inline static Qt::DockWidgetArea toDockWidgetArea(int pos)
1493{
1494 return QDockWidgetPrivate::toDockWidgetArea(pos: static_cast<QInternal::DockPosition>(pos));
1495}
1496
1497// Checks if QDockWidgetGroupWindow or QDockWidget can be plugged the area indicated by path.
1498// Returns false if called with invalid widget type or if compiled without dockwidget support.
1499#if QT_CONFIG(dockwidget)
1500static bool isAreaAllowed(QWidget *widget, const QList<int> &path)
1501{
1502 Q_ASSERT_X((path.size() > 1), "isAreaAllowed", "invalid path size");
1503 const Qt::DockWidgetArea area = toDockWidgetArea(pos: path[1]);
1504
1505 // Read permissions directly from a single dock widget
1506 if (QDockWidget *dw = qobject_cast<QDockWidget *>(object: widget)) {
1507 const bool allowed = dw->isAreaAllowed(area);
1508 if (!allowed)
1509 qCDebug(lcQpaDockWidgets) << "No permission for single DockWidget" << widget << "to dock on" << area;
1510 return allowed;
1511 }
1512
1513 // Read permissions from a DockWidgetGroupWindow depending on its DockWidget children
1514 if (QDockWidgetGroupWindow *dwgw = qobject_cast<QDockWidgetGroupWindow *>(object: widget)) {
1515 const QList<QDockWidget *> children = dwgw->findChildren<QDockWidget *>(aName: QString(), options: Qt::FindDirectChildrenOnly);
1516
1517 if (children.size() == 1) {
1518 // Group window has a single child => read its permissions
1519 const bool allowed = children.at(i: 0)->isAreaAllowed(area);
1520 if (!allowed)
1521 qCDebug(lcQpaDockWidgets) << "No permission for DockWidgetGroupWindow" << widget << "to dock on" << area;
1522 return allowed;
1523 } else {
1524 // Group window has more than one or no children => dock it anywhere
1525 qCDebug(lcQpaDockWidgets) << "DockWidgetGroupWindow" << widget << "has" << children.size() << "children:";
1526 qCDebug(lcQpaDockWidgets) << children;
1527 qCDebug(lcQpaDockWidgets) << "DockWidgetGroupWindow" << widget << "can dock at" << area << "and anywhere else.";
1528 return true;
1529 }
1530 }
1531 qCDebug(lcQpaDockWidgets) << "Docking requested for invalid widget type (coding error)." << widget << area;
1532 return false;
1533}
1534#endif
1535
1536void QMainWindowLayout::setCorner(Qt::Corner corner, Qt::DockWidgetArea area)
1537{
1538 if (layoutState.dockAreaLayout.corners[corner] == area)
1539 return;
1540 layoutState.dockAreaLayout.corners[corner] = area;
1541 if (savedState.isValid())
1542 savedState.dockAreaLayout.corners[corner] = area;
1543 invalidate();
1544}
1545
1546Qt::DockWidgetArea QMainWindowLayout::corner(Qt::Corner corner) const
1547{
1548 return layoutState.dockAreaLayout.corners[corner];
1549}
1550
1551// Returns the rectangle of a dockWidgetArea
1552// if max is true, the maximum possible rectangle for dropping is returned
1553// the current visible rectangle otherwise
1554#if QT_CONFIG(dockwidget)
1555QRect QMainWindowLayout::dockWidgetAreaRect(const Qt::DockWidgetArea area, DockWidgetAreaSize size) const
1556{
1557 const QInternal::DockPosition dockPosition = toDockPos(area);
1558
1559 // Called with invalid dock widget area
1560 if (dockPosition == QInternal::DockCount) {
1561 qCDebug(lcQpaDockWidgets) << "QMainWindowLayout::dockWidgetAreaRect called with" << area;
1562 return QRect();
1563 }
1564
1565 const QDockAreaLayout dl = layoutState.dockAreaLayout;
1566
1567 // Return maximum or visible rectangle
1568 return (size == Maximum) ? dl.gapRect(dockPos: dockPosition) : dl.docks[dockPosition].rect;
1569}
1570#endif
1571
1572void QMainWindowLayout::addDockWidget(Qt::DockWidgetArea area,
1573 QDockWidget *dockwidget,
1574 Qt::Orientation orientation)
1575{
1576 addChildWidget(w: dockwidget);
1577
1578 // If we are currently moving a separator, then we need to abort the move, since each
1579 // time we move the mouse layoutState is replaced by savedState modified by the move.
1580 if (!movingSeparator.isEmpty())
1581 endSeparatorMove(pos: movingSeparatorPos);
1582
1583 layoutState.dockAreaLayout.addDockWidget(pos: toDockPos(area), dockWidget: dockwidget, orientation);
1584 emit dockwidget->dockLocationChanged(area);
1585 invalidate();
1586}
1587
1588bool QMainWindowLayout::restoreDockWidget(QDockWidget *dockwidget)
1589{
1590 addChildWidget(w: dockwidget);
1591 if (!layoutState.dockAreaLayout.restoreDockWidget(dockWidget: dockwidget))
1592 return false;
1593 emit dockwidget->dockLocationChanged(area: dockWidgetArea(widget: dockwidget));
1594 invalidate();
1595 return true;
1596}
1597
1598#if QT_CONFIG(tabbar)
1599void QMainWindowLayout::tabifyDockWidget(QDockWidget *first, QDockWidget *second)
1600{
1601 addChildWidget(w: second);
1602 layoutState.dockAreaLayout.tabifyDockWidget(first, second);
1603 emit second->dockLocationChanged(area: dockWidgetArea(widget: first));
1604 invalidate();
1605}
1606
1607bool QMainWindowLayout::documentMode() const
1608{
1609 return _documentMode;
1610}
1611
1612void QMainWindowLayout::setDocumentMode(bool enabled)
1613{
1614 if (_documentMode == enabled)
1615 return;
1616
1617 _documentMode = enabled;
1618
1619 // Update the document mode for all tab bars
1620 for (QTabBar *bar : std::as_const(t&: usedTabBars))
1621 bar->setDocumentMode(_documentMode);
1622 for (QTabBar *bar : std::as_const(t&: unusedTabBars))
1623 bar->setDocumentMode(_documentMode);
1624}
1625
1626void QMainWindowLayout::setVerticalTabsEnabled(bool enabled)
1627{
1628 if (verticalTabsEnabled == enabled)
1629 return;
1630
1631 verticalTabsEnabled = enabled;
1632
1633 updateTabBarShapes();
1634}
1635
1636#if QT_CONFIG(tabwidget)
1637QTabWidget::TabShape QMainWindowLayout::tabShape() const
1638{
1639 return _tabShape;
1640}
1641
1642void QMainWindowLayout::setTabShape(QTabWidget::TabShape tabShape)
1643{
1644 if (_tabShape == tabShape)
1645 return;
1646
1647 _tabShape = tabShape;
1648
1649 updateTabBarShapes();
1650}
1651
1652QTabWidget::TabPosition QMainWindowLayout::tabPosition(Qt::DockWidgetArea area) const
1653{
1654 const QInternal::DockPosition dockPos = toDockPos(area);
1655 if (dockPos < QInternal::DockCount)
1656 return tabPositions[dockPos];
1657 qWarning(msg: "QMainWindowLayout::tabPosition called with out-of-bounds value '%d'", int(area));
1658 return QTabWidget::North;
1659}
1660
1661void QMainWindowLayout::setTabPosition(Qt::DockWidgetAreas areas, QTabWidget::TabPosition tabPosition)
1662{
1663 const Qt::DockWidgetArea dockWidgetAreas[] = {
1664 Qt::TopDockWidgetArea,
1665 Qt::LeftDockWidgetArea,
1666 Qt::BottomDockWidgetArea,
1667 Qt::RightDockWidgetArea
1668 };
1669 const QInternal::DockPosition dockPositions[] = {
1670 QInternal::TopDock,
1671 QInternal::LeftDock,
1672 QInternal::BottomDock,
1673 QInternal::RightDock
1674 };
1675
1676 for (int i = 0; i < QInternal::DockCount; ++i)
1677 if (areas & dockWidgetAreas[i])
1678 tabPositions[dockPositions[i]] = tabPosition;
1679
1680 updateTabBarShapes();
1681}
1682
1683QTabBar::Shape _q_tb_tabBarShapeFrom(QTabWidget::TabShape shape, QTabWidget::TabPosition position);
1684#endif // QT_CONFIG(tabwidget)
1685
1686void QMainWindowLayout::updateTabBarShapes()
1687{
1688#if QT_CONFIG(tabwidget)
1689 const QTabWidget::TabPosition vertical[] = {
1690 QTabWidget::West,
1691 QTabWidget::East,
1692 QTabWidget::North,
1693 QTabWidget::South
1694 };
1695#else
1696 const QTabBar::Shape vertical[] = {
1697 QTabBar::RoundedWest,
1698 QTabBar::RoundedEast,
1699 QTabBar::RoundedNorth,
1700 QTabBar::RoundedSouth
1701 };
1702#endif
1703
1704 QDockAreaLayout &layout = layoutState.dockAreaLayout;
1705
1706 for (int i = 0; i < QInternal::DockCount; ++i) {
1707#if QT_CONFIG(tabwidget)
1708 QTabWidget::TabPosition pos = verticalTabsEnabled ? vertical[i] : tabPositions[i];
1709 QTabBar::Shape shape = _q_tb_tabBarShapeFrom(shape: _tabShape, position: pos);
1710#else
1711 QTabBar::Shape shape = verticalTabsEnabled ? vertical[i] : QTabBar::RoundedSouth;
1712#endif
1713 layout.docks[i].setTabBarShape(shape);
1714 }
1715}
1716#endif // QT_CONFIG(tabbar)
1717
1718void QMainWindowLayout::splitDockWidget(QDockWidget *after,
1719 QDockWidget *dockwidget,
1720 Qt::Orientation orientation)
1721{
1722 addChildWidget(w: dockwidget);
1723 layoutState.dockAreaLayout.splitDockWidget(after, dockWidget: dockwidget, orientation);
1724 emit dockwidget->dockLocationChanged(area: dockWidgetArea(widget: after));
1725 invalidate();
1726}
1727
1728Qt::DockWidgetArea QMainWindowLayout::dockWidgetArea(QWidget *widget) const
1729{
1730 const QList<int> pathToWidget = layoutState.dockAreaLayout.indexOf(dockWidget: widget);
1731 if (pathToWidget.isEmpty())
1732 return Qt::NoDockWidgetArea;
1733 return toDockWidgetArea(pos: pathToWidget.first());
1734}
1735
1736void QMainWindowLayout::keepSize(QDockWidget *w)
1737{
1738 layoutState.dockAreaLayout.keepSize(w);
1739}
1740
1741#if QT_CONFIG(tabbar)
1742
1743// Handle custom tooltip, and allow to drag tabs away.
1744class QMainWindowTabBar : public QTabBar
1745{
1746 QMainWindow *mainWindow;
1747 QPointer<QDockWidget> draggingDock; // Currently dragging (detached) dock widget
1748public:
1749 QMainWindowTabBar(QMainWindow *parent);
1750protected:
1751 bool event(QEvent *e) override;
1752 void mouseReleaseEvent(QMouseEvent*) override;
1753 void mouseMoveEvent(QMouseEvent*) override;
1754
1755};
1756
1757QMainWindowTabBar::QMainWindowTabBar(QMainWindow *parent)
1758 : QTabBar(parent), mainWindow(parent)
1759{
1760 setExpanding(false);
1761}
1762
1763void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e)
1764{
1765 // The QTabBar handles the moving (reordering) of tabs.
1766 // When QTabBarPrivate::dragInProgress is true, and that the mouse is outside of a region
1767 // around the QTabBar, we will consider the user wants to drag that QDockWidget away from this
1768 // tab area.
1769
1770 QTabBarPrivate *d = static_cast<QTabBarPrivate*>(d_ptr.data());
1771 if (!draggingDock && (mainWindow->dockOptions() & QMainWindow::GroupedDragging)) {
1772 int offset = QApplication::startDragDistance() + 1;
1773 offset *= 3;
1774 QRect r = rect().adjusted(xp1: -offset, yp1: -offset, xp2: offset, yp2: offset);
1775 if (d->dragInProgress && !r.contains(p: e->position().toPoint()) && d->validIndex(index: d->pressedIndex)) {
1776 QMainWindowLayout* mlayout = qt_mainwindow_layout(window: mainWindow);
1777 QDockAreaLayoutInfo *info = mlayout->dockInfo(w: this);
1778 Q_ASSERT(info);
1779 int idx = info->tabIndexToListIndex(d->pressedIndex);
1780 const QDockAreaLayoutItem &item = info->item_list.at(i: idx);
1781 if (item.widgetItem
1782 && (draggingDock = qobject_cast<QDockWidget *>(object: item.widgetItem->widget()))) {
1783 // We should drag this QDockWidget away by unpluging it.
1784 // First cancel the QTabBar's internal move
1785 d->moveTabFinished(index: d->pressedIndex);
1786 d->pressedIndex = -1;
1787 if (d->movingTab)
1788 d->movingTab->setVisible(false);
1789 d->dragStartPosition = QPoint();
1790
1791 // Then starts the drag using QDockWidgetPrivate's API
1792 QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(o: draggingDock));
1793 QDockWidgetLayout *dwlayout = static_cast<QDockWidgetLayout *>(draggingDock->layout());
1794 dockPriv->initDrag(pos: dwlayout->titleArea().center(), nca: true);
1795 dockPriv->startDrag(group: false);
1796 if (dockPriv->state)
1797 dockPriv->state->ctrlDrag = e->modifiers() & Qt::ControlModifier;
1798 }
1799 }
1800 }
1801
1802 if (draggingDock) {
1803 QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(o: draggingDock));
1804 if (dockPriv->state && dockPriv->state->dragging) {
1805 QPoint pos = e->globalPosition().toPoint() - dockPriv->state->pressPos;
1806 draggingDock->move(pos);
1807 // move will call QMainWindowLayout::hover
1808 }
1809 }
1810 QTabBar::mouseMoveEvent(e);
1811}
1812
1813void QMainWindowTabBar::mouseReleaseEvent(QMouseEvent *e)
1814{
1815 if (draggingDock && e->button() == Qt::LeftButton) {
1816 QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(o: draggingDock));
1817 if (dockPriv->state && dockPriv->state->dragging) {
1818 dockPriv->endDrag();
1819 }
1820 draggingDock = nullptr;
1821 }
1822 QTabBar::mouseReleaseEvent(e);
1823}
1824
1825bool QMainWindowTabBar::event(QEvent *e)
1826{
1827 // show the tooltip if tab is too small to fit label
1828
1829 if (e->type() != QEvent::ToolTip)
1830 return QTabBar::event(e);
1831 QSize size = this->size();
1832 QSize hint = sizeHint();
1833 if (shape() == QTabBar::RoundedWest || shape() == QTabBar::RoundedEast) {
1834 size = size.transposed();
1835 hint = hint.transposed();
1836 }
1837 if (size.width() < hint.width())
1838 return QTabBar::event(e);
1839 e->accept();
1840 return true;
1841}
1842
1843QTabBar *QMainWindowLayout::getTabBar()
1844{
1845 if (!usedTabBars.isEmpty() && !isInRestoreState) {
1846 /*
1847 If dock widgets have been removed and added while the main window was
1848 hidden, then the layout hasn't been activated yet, and tab bars from empty
1849 docking areas haven't been put in the cache yet.
1850 */
1851 activate();
1852 }
1853
1854 QTabBar *result = nullptr;
1855 if (!unusedTabBars.isEmpty()) {
1856 result = unusedTabBars.takeLast();
1857 } else {
1858 result = new QMainWindowTabBar(static_cast<QMainWindow *>(parentWidget()));
1859 result->setDrawBase(true);
1860 result->setElideMode(Qt::ElideRight);
1861 result->setDocumentMode(_documentMode);
1862 result->setMovable(true);
1863 connect(sender: result, SIGNAL(currentChanged(int)), receiver: this, SLOT(tabChanged()));
1864 connect(sender: result, signal: &QTabBar::tabMoved, context: this, slot: &QMainWindowLayout::tabMoved);
1865 }
1866
1867 usedTabBars.insert(value: result);
1868 return result;
1869}
1870
1871// Allocates a new separator widget if needed
1872QWidget *QMainWindowLayout::getSeparatorWidget()
1873{
1874 QWidget *result = nullptr;
1875 if (!unusedSeparatorWidgets.isEmpty()) {
1876 result = unusedSeparatorWidgets.takeLast();
1877 } else {
1878 result = new QWidget(parentWidget());
1879 result->setAttribute(Qt::WA_MouseNoMask, on: true);
1880 result->setAutoFillBackground(false);
1881 result->setObjectName("qt_qmainwindow_extended_splitter"_L1);
1882 }
1883 usedSeparatorWidgets.insert(value: result);
1884 return result;
1885}
1886
1887/*! \internal
1888 Returns a pointer QDockAreaLayoutInfo which contains this \a widget directly
1889 (in its internal list)
1890 */
1891QDockAreaLayoutInfo *QMainWindowLayout::dockInfo(QWidget *widget)
1892{
1893 QDockAreaLayoutInfo *info = layoutState.dockAreaLayout.info(widget);
1894 if (info)
1895 return info;
1896 const auto groups =
1897 parent()->findChildren<QDockWidgetGroupWindow*>(options: Qt::FindDirectChildrenOnly);
1898 for (QDockWidgetGroupWindow *dwgw : groups) {
1899 info = dwgw->layoutInfo()->info(widget);
1900 if (info)
1901 return info;
1902 }
1903 return nullptr;
1904}
1905
1906void QMainWindowLayout::tabChanged()
1907{
1908 QTabBar *tb = qobject_cast<QTabBar*>(object: sender());
1909 if (tb == nullptr)
1910 return;
1911 QDockAreaLayoutInfo *info = dockInfo(widget: tb);
1912 if (info == nullptr)
1913 return;
1914
1915 QDockWidget *activated = info->apply(animate: false);
1916
1917 if (activated)
1918 emit static_cast<QMainWindow *>(parentWidget())->tabifiedDockWidgetActivated(dockWidget: activated);
1919
1920 if (auto dwgw = qobject_cast<QDockWidgetGroupWindow*>(object: tb->parentWidget()))
1921 dwgw->adjustFlags();
1922
1923 if (QWidget *w = centralWidget())
1924 w->raise();
1925}
1926
1927void QMainWindowLayout::tabMoved(int from, int to)
1928{
1929 QTabBar *tb = qobject_cast<QTabBar*>(object: sender());
1930 Q_ASSERT(tb);
1931 QDockAreaLayoutInfo *info = dockInfo(widget: tb);
1932 Q_ASSERT(info);
1933
1934 info->moveTab(from, to);
1935}
1936
1937void QMainWindowLayout::raise(QDockWidget *widget)
1938{
1939 QDockAreaLayoutInfo *info = dockInfo(widget);
1940 if (info == nullptr)
1941 return;
1942 if (!info->tabbed)
1943 return;
1944 info->setCurrentTab(widget);
1945}
1946#endif // QT_CONFIG(tabbar)
1947
1948#endif // QT_CONFIG(dockwidget)
1949
1950
1951/******************************************************************************
1952** QMainWindowLayoutState - layout interface
1953*/
1954
1955int QMainWindowLayout::count() const
1956{
1957 int result = 0;
1958 while (itemAt(index: result))
1959 ++result;
1960 return result;
1961}
1962
1963QLayoutItem *QMainWindowLayout::itemAt(int index) const
1964{
1965 int x = 0;
1966
1967 if (QLayoutItem *ret = layoutState.itemAt(index, x: &x))
1968 return ret;
1969
1970 if (statusbar && x++ == index)
1971 return statusbar;
1972
1973 return nullptr;
1974}
1975
1976QLayoutItem *QMainWindowLayout::takeAt(int index)
1977{
1978 int x = 0;
1979
1980 if (QLayoutItem *ret = layoutState.takeAt(index, x: &x)) {
1981 // the widget might in fact have been destroyed by now
1982 if (QWidget *w = ret->widget()) {
1983 widgetAnimator.abort(widget: w);
1984 if (w == pluggingWidget)
1985 pluggingWidget = nullptr;
1986 }
1987
1988 if (savedState.isValid() ) {
1989 //we need to remove the item also from the saved state to prevent crash
1990 savedState.remove(item: ret);
1991 //Also, the item may be contained several times as a gap item.
1992 layoutState.remove(item: ret);
1993 }
1994
1995#if QT_CONFIG(toolbar)
1996 if (!currentGapPos.isEmpty() && currentGapPos.constFirst() == 0) {
1997 currentGapPos = layoutState.toolBarAreaLayout.currentGapIndex();
1998 if (!currentGapPos.isEmpty()) {
1999 currentGapPos.prepend(t: 0);
2000 currentGapRect = layoutState.itemRect(path: currentGapPos);
2001 }
2002 }
2003#endif
2004
2005 return ret;
2006 }
2007
2008 if (statusbar && x++ == index) {
2009 QLayoutItem *ret = statusbar;
2010 statusbar = nullptr;
2011 return ret;
2012 }
2013
2014 return nullptr;
2015}
2016
2017void QMainWindowLayout::setGeometry(const QRect &_r)
2018{
2019 if (savedState.isValid())
2020 return;
2021
2022 QRect r = _r;
2023
2024 QLayout::setGeometry(r);
2025
2026 if (statusbar) {
2027 QRect sbr(QPoint(r.left(), 0),
2028 QSize(r.width(), statusbar->heightForWidth(r.width()))
2029 .expandedTo(otherSize: statusbar->minimumSize()));
2030 sbr.moveBottom(pos: r.bottom());
2031 QRect vr = QStyle::visualRect(direction: parentWidget()->layoutDirection(), boundingRect: _r, logicalRect: sbr);
2032 statusbar->setGeometry(vr);
2033 r.setBottom(sbr.top() - 1);
2034 }
2035
2036 if (restoredState) {
2037 /*
2038 The main window was hidden and was going to be maximized or full-screened when
2039 the state was restored. The state might have been for a larger window size than
2040 the current size (in _r), and the window might still be in the process of being
2041 shown and transitioning to the final size (there's no reliable way of knowing
2042 this across different platforms). Try again with the restored state.
2043 */
2044 layoutState = *restoredState;
2045 if (restoredState->fits()) {
2046 restoredState.reset();
2047 discardRestoredStateTimer.stop();
2048 } else {
2049 /*
2050 Try again in the next setGeometry call, but discard the restored state
2051 after 150ms without any further tries. That's a reasonably short amount of
2052 time during which we can expect the windowing system to either have completed
2053 showing the window, or resized the window once more (which then restarts the
2054 timer in timerEvent).
2055 If the windowing system is done, then the user won't have had a chance to
2056 change the layout interactively AND trigger another resize.
2057 */
2058 discardRestoredStateTimer.start(msec: 150, obj: this);
2059 }
2060 }
2061
2062 layoutState.rect = r;
2063
2064 layoutState.fitLayout();
2065 applyState(newState&: layoutState, animate: false);
2066}
2067
2068void QMainWindowLayout::timerEvent(QTimerEvent *e)
2069{
2070 if (e->timerId() == discardRestoredStateTimer.timerId()) {
2071 discardRestoredStateTimer.stop();
2072 restoredState.reset();
2073 }
2074 QLayout::timerEvent(event: e);
2075}
2076
2077void QMainWindowLayout::addItem(QLayoutItem *)
2078{ qWarning(msg: "QMainWindowLayout::addItem: Please use the public QMainWindow API instead"); }
2079
2080QSize QMainWindowLayout::sizeHint() const
2081{
2082 if (!szHint.isValid()) {
2083 szHint = layoutState.sizeHint();
2084 const QSize sbHint = statusbar ? statusbar->sizeHint() : QSize(0, 0);
2085 szHint = QSize(qMax(a: sbHint.width(), b: szHint.width()),
2086 sbHint.height() + szHint.height());
2087 }
2088 return szHint;
2089}
2090
2091QSize QMainWindowLayout::minimumSize() const
2092{
2093 if (!minSize.isValid()) {
2094 minSize = layoutState.minimumSize();
2095 const QSize sbMin = statusbar ? statusbar->minimumSize() : QSize(0, 0);
2096 minSize = QSize(qMax(a: sbMin.width(), b: minSize.width()),
2097 sbMin.height() + minSize.height());
2098 }
2099 return minSize;
2100}
2101
2102void QMainWindowLayout::invalidate()
2103{
2104 QLayout::invalidate();
2105 minSize = szHint = QSize();
2106}
2107
2108#if QT_CONFIG(dockwidget)
2109void QMainWindowLayout::setCurrentHoveredFloat(QDockWidgetGroupWindow *w)
2110{
2111 if (currentHoveredFloat != w) {
2112 if (currentHoveredFloat) {
2113 disconnect(sender: currentHoveredFloat.data(), signal: &QObject::destroyed,
2114 receiver: this, slot: &QMainWindowLayout::updateGapIndicator);
2115 disconnect(sender: currentHoveredFloat.data(), signal: &QDockWidgetGroupWindow::resized,
2116 receiver: this, slot: &QMainWindowLayout::updateGapIndicator);
2117 if (currentHoveredFloat)
2118 currentHoveredFloat->restore();
2119 } else if (w) {
2120 restore(keepSavedState: true);
2121 }
2122
2123 currentHoveredFloat = w;
2124
2125 if (w) {
2126 connect(sender: w, signal: &QObject::destroyed,
2127 context: this, slot: &QMainWindowLayout::updateGapIndicator, type: Qt::UniqueConnection);
2128 connect(sender: w, signal: &QDockWidgetGroupWindow::resized,
2129 context: this, slot: &QMainWindowLayout::updateGapIndicator, type: Qt::UniqueConnection);
2130 }
2131
2132 updateGapIndicator();
2133 }
2134}
2135#endif // QT_CONFIG(dockwidget)
2136
2137/******************************************************************************
2138** QMainWindowLayout - remaining stuff
2139*/
2140
2141static void fixToolBarOrientation(QLayoutItem *item, int dockPos)
2142{
2143#if QT_CONFIG(toolbar)
2144 QToolBar *toolBar = qobject_cast<QToolBar*>(object: item->widget());
2145 if (toolBar == nullptr)
2146 return;
2147
2148 QRect oldGeo = toolBar->geometry();
2149
2150 QInternal::DockPosition pos
2151 = static_cast<QInternal::DockPosition>(dockPos);
2152 Qt::Orientation o = pos == QInternal::TopDock || pos == QInternal::BottomDock
2153 ? Qt::Horizontal : Qt::Vertical;
2154 if (o != toolBar->orientation())
2155 toolBar->setOrientation(o);
2156
2157 QSize hint = toolBar->sizeHint().boundedTo(otherSize: toolBar->maximumSize())
2158 .expandedTo(otherSize: toolBar->minimumSize());
2159
2160 if (toolBar->size() != hint) {
2161 QRect newGeo(oldGeo.topLeft(), hint);
2162 if (toolBar->layoutDirection() == Qt::RightToLeft)
2163 newGeo.moveRight(pos: oldGeo.right());
2164 toolBar->setGeometry(newGeo);
2165 }
2166
2167#else
2168 Q_UNUSED(item);
2169 Q_UNUSED(dockPos);
2170#endif
2171}
2172
2173void QMainWindowLayout::revert(QLayoutItem *widgetItem)
2174{
2175 if (!savedState.isValid())
2176 return;
2177
2178 QWidget *widget = widgetItem->widget();
2179 layoutState = savedState;
2180 currentGapPos = layoutState.indexOf(widget);
2181 if (currentGapPos.isEmpty())
2182 return;
2183 fixToolBarOrientation(item: widgetItem, dockPos: currentGapPos.at(i: 1));
2184 layoutState.unplug(path: currentGapPos);
2185 layoutState.fitLayout();
2186 currentGapRect = layoutState.itemRect(path: currentGapPos);
2187
2188 plug(widgetItem);
2189}
2190
2191bool QMainWindowLayout::plug(QLayoutItem *widgetItem)
2192{
2193#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget) && QT_CONFIG(tabbar)
2194 if (currentHoveredFloat) {
2195 QWidget *widget = widgetItem->widget();
2196 QList<int> previousPath = layoutState.indexOf(widget);
2197 if (!previousPath.isEmpty())
2198 layoutState.remove(path: previousPath);
2199 previousPath = currentHoveredFloat->layoutInfo()->indexOf(widget);
2200 // Let's remove the widget from any possible group window
2201 const auto groups =
2202 parent()->findChildren<QDockWidgetGroupWindow*>(options: Qt::FindDirectChildrenOnly);
2203 for (QDockWidgetGroupWindow *dwgw : groups) {
2204 if (dwgw == currentHoveredFloat)
2205 continue;
2206 QList<int> path = dwgw->layoutInfo()->indexOf(widget);
2207 if (!path.isEmpty())
2208 dwgw->layoutInfo()->remove(path);
2209 }
2210 currentGapRect = QRect();
2211 currentHoveredFloat->apply();
2212 if (!previousPath.isEmpty())
2213 currentHoveredFloat->layoutInfo()->remove(path: previousPath);
2214 QRect globalRect = currentHoveredFloat->currentGapRect;
2215 globalRect.moveTopLeft(p: currentHoveredFloat->mapToGlobal(globalRect.topLeft()));
2216 pluggingWidget = widget;
2217 widgetAnimator.animate(widget, final_geometry: globalRect, animate: dockOptions & QMainWindow::AnimatedDocks);
2218 return true;
2219 }
2220#endif
2221
2222 if (!parentWidget()->isVisible() || parentWidget()->isMinimized() || currentGapPos.isEmpty())
2223 return false;
2224
2225 fixToolBarOrientation(item: widgetItem, dockPos: currentGapPos.at(i: 1));
2226
2227 QWidget *widget = widgetItem->widget();
2228
2229#if QT_CONFIG(dockwidget)
2230 // Let's remove the widget from any possible group window
2231 const auto groups =
2232 parent()->findChildren<QDockWidgetGroupWindow*>(options: Qt::FindDirectChildrenOnly);
2233 for (QDockWidgetGroupWindow *dwgw : groups) {
2234 QList<int> path = dwgw->layoutInfo()->indexOf(widget);
2235 if (!path.isEmpty())
2236 dwgw->layoutInfo()->remove(path);
2237 }
2238#endif
2239
2240 QList<int> previousPath = layoutState.indexOf(widget);
2241
2242 const QLayoutItem *it = layoutState.plug(path: currentGapPos);
2243 if (!it)
2244 return false;
2245 Q_ASSERT(it == widgetItem);
2246 if (!previousPath.isEmpty())
2247 layoutState.remove(path: previousPath);
2248
2249 pluggingWidget = widget;
2250 QRect globalRect = currentGapRect;
2251 globalRect.moveTopLeft(p: parentWidget()->mapToGlobal(globalRect.topLeft()));
2252#if QT_CONFIG(dockwidget)
2253 if (qobject_cast<QDockWidget*>(object: widget) != nullptr) {
2254 QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(object: widget->layout());
2255 if (layout->nativeWindowDeco()) {
2256 globalRect.adjust(dx1: 0, dy1: layout->titleHeight(), dx2: 0, dy2: 0);
2257 } else {
2258 int fw = widget->style()->pixelMetric(metric: QStyle::PM_DockWidgetFrameWidth, option: nullptr, widget);
2259 globalRect.adjust(dx1: -fw, dy1: -fw, dx2: fw, dy2: fw);
2260 }
2261 }
2262#endif
2263 widgetAnimator.animate(widget, final_geometry: globalRect, animate: dockOptions & QMainWindow::AnimatedDocks);
2264
2265 return true;
2266}
2267
2268void QMainWindowLayout::animationFinished(QWidget *widget)
2269{
2270 //this function is called from within the Widget Animator whenever an animation is finished
2271 //on a certain widget
2272#if QT_CONFIG(toolbar)
2273 if (QToolBar *tb = qobject_cast<QToolBar*>(object: widget)) {
2274 QToolBarLayout *tbl = qobject_cast<QToolBarLayout*>(object: tb->layout());
2275 if (tbl->animating) {
2276 tbl->animating = false;
2277 if (tbl->expanded)
2278 tbl->layoutActions(size: tb->size());
2279 tb->update();
2280 }
2281 }
2282#endif
2283
2284 if (widget == pluggingWidget) {
2285
2286#if QT_CONFIG(dockwidget)
2287#if QT_CONFIG(tabbar)
2288 if (QDockWidgetGroupWindow *dwgw = qobject_cast<QDockWidgetGroupWindow *>(object: widget)) {
2289 // When the animated widget was a QDockWidgetGroupWindow, it means each of the
2290 // embedded QDockWidget needs to be plugged back into the QMainWindow layout.
2291 savedState.clear();
2292 QDockAreaLayoutInfo *srcInfo = dwgw->layoutInfo();
2293 const QDockAreaLayoutInfo *srcTabInfo = dwgw->tabLayoutInfo();
2294 QDockAreaLayoutInfo *dstParentInfo;
2295 QList<int> dstPath;
2296
2297 if (currentHoveredFloat) {
2298 dstPath = currentHoveredFloat->layoutInfo()->indexOf(widget);
2299 Q_ASSERT(dstPath.size() >= 1);
2300 dstParentInfo = currentHoveredFloat->layoutInfo()->info(path: dstPath);
2301 } else {
2302 dstPath = layoutState.dockAreaLayout.indexOf(dockWidget: widget);
2303 Q_ASSERT(dstPath.size() >= 2);
2304 dstParentInfo = layoutState.dockAreaLayout.info(path: dstPath);
2305 }
2306 Q_ASSERT(dstParentInfo);
2307 int idx = dstPath.constLast();
2308 Q_ASSERT(dstParentInfo->item_list[idx].widgetItem->widget() == dwgw);
2309 if (dstParentInfo->tabbed && srcTabInfo) {
2310 // merge the two tab widgets
2311 delete dstParentInfo->item_list[idx].widgetItem;
2312 dstParentInfo->item_list.removeAt(i: idx);
2313 std::copy(srcTabInfo->item_list.cbegin(), srcTabInfo->item_list.cend(),
2314 std::inserter(x&: dstParentInfo->item_list,
2315 i: dstParentInfo->item_list.begin() + idx));
2316 quintptr currentId = srcTabInfo->currentTabId();
2317 *srcInfo = QDockAreaLayoutInfo();
2318 dstParentInfo->reparentWidgets(p: currentHoveredFloat ? currentHoveredFloat.data()
2319 : parentWidget());
2320 dstParentInfo->updateTabBar();
2321 dstParentInfo->setCurrentTabId(currentId);
2322 } else {
2323 QDockAreaLayoutItem &item = dstParentInfo->item_list[idx];
2324 Q_ASSERT(item.widgetItem->widget() == dwgw);
2325 delete item.widgetItem;
2326 item.widgetItem = nullptr;
2327 item.subinfo = new QDockAreaLayoutInfo(std::move(*srcInfo));
2328 *srcInfo = QDockAreaLayoutInfo();
2329 item.subinfo->reparentWidgets(p: currentHoveredFloat ? currentHoveredFloat.data()
2330 : parentWidget());
2331 item.subinfo->setTabBarShape(dstParentInfo->tabBarShape);
2332 }
2333 dwgw->destroyOrHideIfEmpty();
2334 }
2335#endif
2336
2337 if (QDockWidget *dw = qobject_cast<QDockWidget*>(object: widget)) {
2338 dw->setParent(currentHoveredFloat ? currentHoveredFloat.data() : parentWidget());
2339 dw->show();
2340 dw->d_func()->plug(rect: currentGapRect);
2341 }
2342#endif
2343#if QT_CONFIG(toolbar)
2344 if (QToolBar *tb = qobject_cast<QToolBar*>(object: widget))
2345 tb->d_func()->plug(r: currentGapRect);
2346#endif
2347
2348 savedState.clear();
2349 currentGapPos.clear();
2350 pluggingWidget = nullptr;
2351#if QT_CONFIG(dockwidget)
2352 setCurrentHoveredFloat(nullptr);
2353#endif
2354 //applying the state will make sure that the currentGap is updated correctly
2355 //and all the geometries (especially the one from the central widget) is correct
2356 layoutState.apply(animated: false);
2357
2358#if QT_CONFIG(dockwidget)
2359#if QT_CONFIG(tabbar)
2360 if (qobject_cast<QDockWidget*>(object: widget) != nullptr) {
2361 // info() might return null if the widget is destroyed while
2362 // animating but before the animationFinished signal is received.
2363 if (QDockAreaLayoutInfo *info = dockInfo(widget))
2364 info->setCurrentTab(widget);
2365 }
2366#endif
2367#endif
2368 }
2369
2370 if (!widgetAnimator.animating()) {
2371 //all animations are finished
2372#if QT_CONFIG(dockwidget)
2373 parentWidget()->update(layoutState.dockAreaLayout.separatorRegion());
2374#if QT_CONFIG(tabbar)
2375 const auto usedTabBarsCopy = usedTabBars; // list potentially modified by animations
2376 for (QTabBar *tab_bar : usedTabBarsCopy)
2377 tab_bar->show();
2378#endif // QT_CONFIG(tabbar)
2379#endif // QT_CONFIG(dockwidget)
2380 }
2381
2382 updateGapIndicator();
2383}
2384
2385void QMainWindowLayout::restore(bool keepSavedState)
2386{
2387 if (!savedState.isValid())
2388 return;
2389
2390 layoutState = savedState;
2391 applyState(newState&: layoutState);
2392 if (!keepSavedState)
2393 savedState.clear();
2394 currentGapPos.clear();
2395 pluggingWidget = nullptr;
2396 updateGapIndicator();
2397}
2398
2399QMainWindowLayout::QMainWindowLayout(QMainWindow *mainwindow, QLayout *parentLayout)
2400 : QLayout(parentLayout ? static_cast<QWidget *>(nullptr) : mainwindow)
2401 , layoutState(mainwindow)
2402 , savedState(mainwindow)
2403 , dockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowTabbedDocks)
2404 , statusbar(nullptr)
2405#if QT_CONFIG(dockwidget)
2406#if QT_CONFIG(tabbar)
2407 , _documentMode(false)
2408 , verticalTabsEnabled(false)
2409#if QT_CONFIG(tabwidget)
2410 , _tabShape(QTabWidget::Rounded)
2411#endif
2412#endif
2413#endif // QT_CONFIG(dockwidget)
2414 , widgetAnimator(this)
2415 , pluggingWidget(nullptr)
2416{
2417 if (parentLayout)
2418 setParent(parentLayout);
2419
2420#if QT_CONFIG(dockwidget)
2421#if QT_CONFIG(tabbar)
2422 sep = mainwindow->style()->pixelMetric(metric: QStyle::PM_DockWidgetSeparatorExtent, option: nullptr, widget: mainwindow);
2423#endif
2424
2425#if QT_CONFIG(tabwidget)
2426 for (int i = 0; i < QInternal::DockCount; ++i)
2427 tabPositions[i] = QTabWidget::South;
2428#endif
2429#endif // QT_CONFIG(dockwidget)
2430 pluggingWidget = nullptr;
2431
2432 setObjectName(mainwindow->objectName() + "_layout"_L1);
2433}
2434
2435QMainWindowLayout::~QMainWindowLayout()
2436{
2437 layoutState.deleteAllLayoutItems();
2438 layoutState.deleteCentralWidgetItem();
2439
2440 delete statusbar;
2441}
2442
2443void QMainWindowLayout::setDockOptions(QMainWindow::DockOptions opts)
2444{
2445 if (opts == dockOptions)
2446 return;
2447
2448 dockOptions = opts;
2449
2450#if QT_CONFIG(dockwidget) && QT_CONFIG(tabbar)
2451 setVerticalTabsEnabled(opts & QMainWindow::VerticalTabs);
2452#endif
2453
2454 invalidate();
2455}
2456
2457#if QT_CONFIG(statusbar)
2458QStatusBar *QMainWindowLayout::statusBar() const
2459{ return statusbar ? qobject_cast<QStatusBar *>(object: statusbar->widget()) : 0; }
2460
2461void QMainWindowLayout::setStatusBar(QStatusBar *sb)
2462{
2463 if (sb)
2464 addChildWidget(w: sb);
2465 delete statusbar;
2466 statusbar = sb ? new QWidgetItemV2(sb) : nullptr;
2467 invalidate();
2468}
2469#endif // QT_CONFIG(statusbar)
2470
2471QWidget *QMainWindowLayout::centralWidget() const
2472{
2473 return layoutState.centralWidget();
2474}
2475
2476void QMainWindowLayout::setCentralWidget(QWidget *widget)
2477{
2478 if (widget != nullptr)
2479 addChildWidget(w: widget);
2480 layoutState.setCentralWidget(widget);
2481 if (savedState.isValid()) {
2482#if QT_CONFIG(dockwidget)
2483 savedState.dockAreaLayout.centralWidgetItem = layoutState.dockAreaLayout.centralWidgetItem;
2484 savedState.dockAreaLayout.fallbackToSizeHints = true;
2485#else
2486 savedState.centralWidgetItem = layoutState.centralWidgetItem;
2487#endif
2488 }
2489 invalidate();
2490}
2491
2492#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2493/*! \internal
2494 This helper function is called by QMainWindowLayout::unplug if QMainWindow::GroupedDragging is
2495 set and we are dragging the title bar of a non-floating QDockWidget.
2496 If one should unplug the whole group, do so and return true, otherwise return false.
2497 \a item is pointing to the QLayoutItem that holds the QDockWidget, but will be updated to the
2498 QLayoutItem that holds the new QDockWidgetGroupWindow if the group is unplugged.
2499*/
2500static bool unplugGroup(QMainWindowLayout *layout, QLayoutItem **item,
2501 QDockAreaLayoutItem &parentItem)
2502{
2503 if (!parentItem.subinfo || !parentItem.subinfo->tabbed)
2504 return false;
2505
2506 // The QDockWidget is part of a group of tab and we need to unplug them all.
2507 QDockWidgetGroupWindow *floatingTabs = layout->createTabbedDockWindow();
2508 QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
2509 *info = std::move(*parentItem.subinfo);
2510 delete parentItem.subinfo;
2511 parentItem.subinfo = nullptr;
2512 floatingTabs->setGeometry(info->rect.translated(p: layout->parentWidget()->pos()));
2513 floatingTabs->show();
2514 floatingTabs->raise();
2515 *item = new QDockWidgetGroupWindowItem(floatingTabs);
2516 parentItem.widgetItem = *item;
2517 return true;
2518}
2519#endif
2520
2521#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2522static QTabBar::Shape tabwidgetPositionToTabBarShape(QWidget *w)
2523{
2524 QTabBar::Shape result = QTabBar::RoundedSouth;
2525 if (qobject_cast<QDockWidget *>(object: w)) {
2526 switch (static_cast<QDockWidgetPrivate *>(qt_widget_private(widget: w))->tabPosition) {
2527 case QTabWidget::North:
2528 result = QTabBar::RoundedNorth;
2529 break;
2530 case QTabWidget::South:
2531 result = QTabBar::RoundedSouth;
2532 break;
2533 case QTabWidget::West:
2534 result = QTabBar::RoundedWest;
2535 break;
2536 case QTabWidget::East:
2537 result = QTabBar::RoundedEast;
2538 break;
2539 }
2540 }
2541 return result;
2542}
2543#endif // QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2544
2545/*! \internal
2546 Unplug \a widget (QDockWidget or QToolBar) from it's parent container.
2547
2548 If \a group is true we might actually unplug the group of tabs this
2549 widget is part if QMainWindow::GroupedDragging is set. When \a group
2550 is false, the widget itself is always unplugged alone
2551
2552 Returns the QLayoutItem of the dragged element.
2553 The layout item is kept in the layout but set as a gap item.
2554 */
2555QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group)
2556{
2557#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2558 auto *groupWindow = qobject_cast<const QDockWidgetGroupWindow *>(object: widget->parentWidget());
2559 if (!widget->isWindow() && groupWindow) {
2560 if (group && groupWindow->tabLayoutInfo()) {
2561 // We are just dragging a floating window as it, not need to do anything, we just have to
2562 // look up the corresponding QWidgetItem* if it exists
2563 if (QDockAreaLayoutInfo *info = dockInfo(widget: widget->parentWidget())) {
2564 QList<int> groupWindowPath = info->indexOf(widget: widget->parentWidget());
2565 return groupWindowPath.isEmpty() ? nullptr : info->item(path: groupWindowPath).widgetItem;
2566 }
2567 qCDebug(lcQpaDockWidgets) << "Drag only:" << widget << "Group:" << group;
2568 return nullptr;
2569 }
2570 QList<int> path = groupWindow->layoutInfo()->indexOf(widget);
2571 QDockAreaLayoutItem parentItem = groupWindow->layoutInfo()->item(path);
2572 QLayoutItem *item = parentItem.widgetItem;
2573 if (group && path.size() > 1
2574 && unplugGroup(layout: this, item: &item, parentItem)) {
2575 qCDebug(lcQpaDockWidgets) << "Unplugging:" << widget << "from" << item;
2576 return item;
2577 } else {
2578 // We are unplugging a single dock widget from a floating window.
2579 QDockWidget *dockWidget = qobject_cast<QDockWidget *>(object: widget);
2580 Q_ASSERT(dockWidget); // cannot be a QDockWidgetGroupWindow because it's not floating.
2581
2582 // unplug the widget first
2583 dockWidget->d_func()->unplug(rect: widget->geometry());
2584
2585 // Create a floating tab, copy properties and generate layout info
2586 QDockWidgetGroupWindow *floatingTabs = createTabbedDockWindow();
2587 const QInternal::DockPosition dockPos = groupWindow->layoutInfo()->dockPos;
2588 QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
2589
2590 const QTabBar::Shape shape = tabwidgetPositionToTabBarShape(w: dockWidget);
2591
2592 // Populate newly created DockAreaLayoutInfo of floating tabs
2593 *info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, dockPos,
2594 Qt::Horizontal, shape,
2595 layoutState.mainWindow);
2596
2597 // Create tab and hide it as group window contains only one widget
2598 info->tabbed = true;
2599 info->tabBar = getTabBar();
2600 info->tabBar->hide();
2601 updateGapIndicator();
2602
2603 // Reparent it to a QDockWidgetGroupLayout
2604 floatingTabs->setGeometry(dockWidget->geometry());
2605
2606 // Append reference to floatingTabs to the dock's item_list
2607 parentItem.widgetItem = new QDockWidgetGroupWindowItem(floatingTabs);
2608 layoutState.dockAreaLayout.docks[dockPos].item_list.append(t: parentItem);
2609
2610 // use populated parentItem to set reference to dockWidget as the first item in own list
2611 parentItem.widgetItem = new QDockWidgetItem(dockWidget);
2612 info->item_list = {parentItem};
2613
2614 // Add non-gap items of the dock to the tab bar
2615 for (const auto &listItem : layoutState.dockAreaLayout.docks[dockPos].item_list) {
2616 if (listItem.GapItem || !listItem.widgetItem)
2617 continue;
2618 info->tabBar->addTab(text: listItem.widgetItem->widget()->objectName());
2619 }
2620
2621 // Re-parent and fit
2622 floatingTabs->setParent(layoutState.mainWindow);
2623 floatingTabs->layoutInfo()->fitItems();
2624 floatingTabs->layoutInfo()->apply(animate: dockOptions & QMainWindow::AnimatedDocks);
2625 groupWindow->layoutInfo()->fitItems();
2626 groupWindow->layoutInfo()->apply(animate: dockOptions & QMainWindow::AnimatedDocks);
2627 dockWidget->d_func()->tabPosition = layoutState.mainWindow->tabPosition(area: toDockWidgetArea(pos: dockPos));
2628 info->reparentWidgets(p: floatingTabs);
2629 dockWidget->setParent(floatingTabs);
2630 info->updateTabBar();
2631
2632 // Show the new item
2633 const QList<int> path = layoutState.indexOf(widget: floatingTabs);
2634 QRect r = layoutState.itemRect(path);
2635 savedState = layoutState;
2636 savedState.fitLayout();
2637
2638 // Update gap, fix orientation, raise and show
2639 currentGapPos = path;
2640 currentGapRect = r;
2641 updateGapIndicator();
2642 fixToolBarOrientation(item: parentItem.widgetItem, dockPos: currentGapPos.at(i: 1));
2643 floatingTabs->show();
2644 floatingTabs->raise();
2645
2646 qCDebug(lcQpaDockWidgets) << "Unplugged from floating dock:" << widget << "from" << parentItem.widgetItem;
2647 return parentItem.widgetItem;
2648 }
2649 }
2650#endif
2651 QList<int> path = layoutState.indexOf(widget);
2652 if (path.isEmpty())
2653 return nullptr;
2654
2655 QLayoutItem *item = layoutState.item(path);
2656 if (widget->isWindow())
2657 return item;
2658
2659 QRect r = layoutState.itemRect(path);
2660 savedState = layoutState;
2661
2662#if QT_CONFIG(dockwidget)
2663 if (QDockWidget *dw = qobject_cast<QDockWidget*>(object: widget)) {
2664 Q_ASSERT(path.constFirst() == 1);
2665#if QT_CONFIG(tabwidget)
2666 if (group && (dockOptions & QMainWindow::GroupedDragging) && path.size() > 3
2667 && unplugGroup(layout: this, item: &item,
2668 parentItem&: layoutState.dockAreaLayout.item(path: path.mid(pos: 1, len: path.size() - 2)))) {
2669 path.removeLast();
2670 savedState = layoutState;
2671 } else
2672#endif // QT_CONFIG(tabwidget)
2673 {
2674 // Dock widget is unplugged from a main window dock
2675 // => height or width need to be decreased by separator size
2676 switch (dockWidgetArea(widget: dw)) {
2677 case Qt::LeftDockWidgetArea:
2678 case Qt::RightDockWidgetArea:
2679 r.setHeight(r.height() - sep);
2680 break;
2681 case Qt::TopDockWidgetArea:
2682 case Qt::BottomDockWidgetArea:
2683 r.setWidth(r.width() - sep);
2684 break;
2685 case Qt::NoDockWidgetArea:
2686 case Qt::DockWidgetArea_Mask:
2687 break;
2688 }
2689
2690 // Depending on the title bar layout (vertical / horizontal),
2691 // width and height have to provide minimum space for window handles
2692 // and mouse dragging.
2693 // Assuming horizontal title bar, if the dock widget does not have a layout.
2694 const auto *layout = qobject_cast<QDockWidgetLayout *>(object: dw->layout());
2695 const bool verticalTitleBar = layout ? layout->verticalTitleBar : false;
2696 const int tbHeight = QApplication::style()
2697 ? QApplication::style()->pixelMetric(metric: QStyle::PixelMetric::PM_TitleBarHeight)
2698 : 20;
2699 const int minHeight = verticalTitleBar ? 2 * tbHeight : tbHeight;
2700 const int minWidth = verticalTitleBar ? tbHeight : 2 * tbHeight;
2701 r.setSize(r.size().expandedTo(otherSize: QSize(minWidth, minHeight)));
2702 qCDebug(lcQpaDockWidgets) << dw << "will be unplugged with size" << r.size();
2703
2704 dw->d_func()->unplug(rect: r);
2705 }
2706 }
2707#endif // QT_CONFIG(dockwidget)
2708#if QT_CONFIG(toolbar)
2709 if (QToolBar *tb = qobject_cast<QToolBar*>(object: widget)) {
2710 tb->d_func()->unplug(r);
2711 }
2712#endif
2713
2714#if !QT_CONFIG(dockwidget) || !QT_CONFIG(tabbar)
2715 Q_UNUSED(group);
2716#endif
2717
2718 layoutState.unplug(path ,other: &savedState);
2719 savedState.fitLayout();
2720 currentGapPos = path;
2721 currentGapRect = r;
2722 updateGapIndicator();
2723
2724 fixToolBarOrientation(item, dockPos: currentGapPos.at(i: 1));
2725
2726 return item;
2727}
2728
2729void QMainWindowLayout::updateGapIndicator()
2730{
2731#if QT_CONFIG(rubberband)
2732 if (!widgetAnimator.animating() && (!currentGapPos.isEmpty()
2733#if QT_CONFIG(dockwidget)
2734 || currentHoveredFloat
2735#endif
2736 )) {
2737 QWidget *expectedParent =
2738#if QT_CONFIG(dockwidget)
2739 currentHoveredFloat ? currentHoveredFloat.data() :
2740#endif
2741 parentWidget();
2742 if (!gapIndicator) {
2743 gapIndicator = new QRubberBand(QRubberBand::Rectangle, expectedParent);
2744 // For accessibility to identify this special widget.
2745 gapIndicator->setObjectName("qt_rubberband"_L1);
2746 } else if (gapIndicator->parent() != expectedParent) {
2747 gapIndicator->setParent(expectedParent);
2748 }
2749
2750 // Prevent re-entry in case of size change
2751 const bool sigBlockState = gapIndicator->signalsBlocked();
2752 auto resetSignals = qScopeGuard(f: [this, sigBlockState](){ gapIndicator->blockSignals(b: sigBlockState); });
2753 gapIndicator->blockSignals(b: true);
2754
2755#if QT_CONFIG(dockwidget)
2756 if (currentHoveredFloat)
2757 gapIndicator->setGeometry(currentHoveredFloat->currentGapRect);
2758 else
2759#endif
2760 gapIndicator->setGeometry(currentGapRect);
2761
2762 gapIndicator->show();
2763 gapIndicator->raise();
2764
2765 // Reset signal state
2766
2767 } else if (gapIndicator) {
2768 gapIndicator->hide();
2769 }
2770
2771#endif // QT_CONFIG(rubberband)
2772}
2773
2774void QMainWindowLayout::hover(QLayoutItem *hoverTarget,
2775 const QPoint &mousePos) {
2776 if (!parentWidget()->isVisible() || parentWidget()->isMinimized() ||
2777 pluggingWidget != nullptr || hoverTarget == nullptr)
2778 return;
2779
2780 QWidget *widget = hoverTarget->widget();
2781
2782#if QT_CONFIG(dockwidget)
2783 if ((dockOptions & QMainWindow::GroupedDragging) && (qobject_cast<QDockWidget*>(object: widget)
2784 || qobject_cast<QDockWidgetGroupWindow *>(object: widget))) {
2785
2786 // Check if we are over another floating dock widget
2787 QVarLengthArray<QWidget *, 10> candidates;
2788 const auto siblings = parentWidget()->children();
2789 for (QObject *c : siblings) {
2790 QWidget *w = qobject_cast<QWidget*>(o: c);
2791 if (!w)
2792 continue;
2793
2794 // Handle only dock widgets and group windows
2795 if (!qobject_cast<QDockWidget*>(object: w) && !qobject_cast<QDockWidgetGroupWindow *>(object: w))
2796 continue;
2797
2798 // Check permission to dock on another dock widget or floating dock
2799 // FIXME in 6.4
2800
2801 if (w != widget && w->isWindow() && w->isVisible() && !w->isMinimized())
2802 candidates << w;
2803
2804 if (QDockWidgetGroupWindow *group = qobject_cast<QDockWidgetGroupWindow *>(object: w)) {
2805 // floating QDockWidgets have a QDockWidgetGroupWindow as a parent,
2806 // if they have been hovered over
2807 const auto groupChildren = group->children();
2808 for (QObject *c : groupChildren) {
2809 if (QDockWidget *dw = qobject_cast<QDockWidget*>(object: c)) {
2810 if (dw != widget && dw->isFloating() && dw->isVisible() && !dw->isMinimized())
2811 candidates << dw;
2812 }
2813 }
2814 }
2815 }
2816
2817 for (QWidget *w : candidates) {
2818 const QScreen *screen1 = qt_widget_private(widget)->associatedScreen();
2819 const QScreen *screen2 = qt_widget_private(widget: w)->associatedScreen();
2820 if (screen1 && screen2 && screen1 != screen2)
2821 continue;
2822 if (!w->geometry().contains(p: mousePos))
2823 continue;
2824
2825#if QT_CONFIG(tabwidget)
2826 if (auto dropTo = qobject_cast<QDockWidget *>(object: w)) {
2827
2828 // w is the drop target's widget
2829 w = dropTo->widget();
2830
2831 // Create a floating tab, unless already existing
2832 if (!qobject_cast<QDockWidgetGroupWindow *>(object: w)) {
2833 QDockWidgetGroupWindow *floatingTabs = createTabbedDockWindow();
2834 floatingTabs->setGeometry(dropTo->geometry());
2835 QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
2836 const QTabBar::Shape shape = tabwidgetPositionToTabBarShape(w: dropTo);
2837 const QInternal::DockPosition dockPosition = toDockPos(area: dockWidgetArea(widget: dropTo));
2838 *info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, dockPosition,
2839 Qt::Horizontal, shape,
2840 static_cast<QMainWindow *>(parentWidget()));
2841 info->tabBar = getTabBar();
2842 info->tabbed = true;
2843 QLayout *parentLayout = dropTo->parentWidget()->layout();
2844 info->item_list.append(
2845 t: QDockAreaLayoutItem(parentLayout->takeAt(index: parentLayout->indexOf(dropTo))));
2846
2847 dropTo->setParent(floatingTabs);
2848 qCDebug(lcQpaDockWidgets) << "Wrapping" << widget << "into floating tabs" << floatingTabs;
2849 w = floatingTabs;
2850 }
2851
2852 // Show the drop target and raise widget to foreground
2853 dropTo->show();
2854 qCDebug(lcQpaDockWidgets) << "Showing" << dropTo;
2855 widget->raise();
2856 qCDebug(lcQpaDockWidgets) << "Raising" << widget;
2857 }
2858#endif
2859 auto group = qobject_cast<QDockWidgetGroupWindow *>(object: w);
2860 Q_ASSERT(group);
2861 if (group->hover(widgetItem: hoverTarget, mousePos: group->mapFromGlobal(mousePos))) {
2862 setCurrentHoveredFloat(group);
2863 applyState(newState&: layoutState); // update the tabbars
2864 }
2865 return;
2866 }
2867 }
2868 setCurrentHoveredFloat(nullptr);
2869 layoutState.dockAreaLayout.fallbackToSizeHints = false;
2870#endif // QT_CONFIG(dockwidget)
2871
2872 QPoint pos = parentWidget()->mapFromGlobal(mousePos);
2873
2874 if (!savedState.isValid())
2875 savedState = layoutState;
2876
2877 QList<int> path = savedState.gapIndex(widget, pos);
2878
2879 if (!path.isEmpty()) {
2880 bool allowed = false;
2881
2882#if QT_CONFIG(dockwidget)
2883 allowed = isAreaAllowed(widget, path);
2884#endif
2885#if QT_CONFIG(toolbar)
2886 if (QToolBar *tb = qobject_cast<QToolBar*>(object: widget))
2887 allowed = tb->isAreaAllowed(area: toToolBarArea(pos: path.at(i: 1)));
2888#endif
2889
2890 if (!allowed)
2891 path.clear();
2892 }
2893
2894 if (path == currentGapPos)
2895 return; // the gap is already there
2896
2897 currentGapPos = path;
2898 if (path.isEmpty()) {
2899 fixToolBarOrientation(item: hoverTarget, dockPos: 2); // 2 = top dock, ie. horizontal
2900 restore(keepSavedState: true);
2901 return;
2902 }
2903
2904 fixToolBarOrientation(item: hoverTarget, dockPos: currentGapPos.at(i: 1));
2905
2906 QMainWindowLayoutState newState = savedState;
2907
2908 if (!newState.insertGap(path, item: hoverTarget)) {
2909 restore(keepSavedState: true); // not enough space
2910 return;
2911 }
2912
2913 QSize min = newState.minimumSize();
2914 QSize size = newState.rect.size();
2915
2916 if (min.width() > size.width() || min.height() > size.height()) {
2917 restore(keepSavedState: true);
2918 return;
2919 }
2920
2921 newState.fitLayout();
2922
2923 currentGapRect = newState.gapRect(path: currentGapPos);
2924
2925#if QT_CONFIG(dockwidget)
2926 parentWidget()->update(layoutState.dockAreaLayout.separatorRegion());
2927#endif
2928 layoutState = std::move(newState);
2929 applyState(newState&: layoutState);
2930
2931 updateGapIndicator();
2932}
2933
2934#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2935QDockWidgetGroupWindow *QMainWindowLayout::createTabbedDockWindow()
2936{
2937 QDockWidgetGroupWindow* f = new QDockWidgetGroupWindow(parentWidget(), Qt::Tool);
2938 new QDockWidgetGroupLayout(f);
2939 return f;
2940}
2941#endif
2942
2943void QMainWindowLayout::applyState(QMainWindowLayoutState &newState, bool animate)
2944{
2945 // applying the state can lead to showing separator widgets, which would lead to a re-layout
2946 // (even though the separator widgets are not really part of the layout)
2947 // break the loop
2948 if (isInApplyState)
2949 return;
2950 isInApplyState = true;
2951#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2952 QSet<QTabBar*> used = newState.dockAreaLayout.usedTabBars();
2953 const auto groups =
2954 parent()->findChildren<QDockWidgetGroupWindow*>(options: Qt::FindDirectChildrenOnly);
2955 for (QDockWidgetGroupWindow *dwgw : groups)
2956 used += dwgw->layoutInfo()->usedTabBars();
2957
2958 const QSet<QTabBar*> retired = usedTabBars - used;
2959 usedTabBars = used;
2960 for (QTabBar *tab_bar : retired) {
2961 tab_bar->hide();
2962 while (tab_bar->count() > 0)
2963 tab_bar->removeTab(index: 0);
2964 unusedTabBars.append(t: tab_bar);
2965 }
2966
2967 if (sep == 1) {
2968 const QSet<QWidget*> usedSeps = newState.dockAreaLayout.usedSeparatorWidgets();
2969 const QSet<QWidget*> retiredSeps = usedSeparatorWidgets - usedSeps;
2970 usedSeparatorWidgets = usedSeps;
2971 for (QWidget *sepWidget : retiredSeps) {
2972 unusedSeparatorWidgets.append(t: sepWidget);
2973 sepWidget->hide();
2974 }
2975 }
2976
2977 for (int i = 0; i < QInternal::DockCount; ++i)
2978 newState.dockAreaLayout.docks[i].reparentWidgets(p: parentWidget());
2979
2980#endif // QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2981 newState.apply(animated: dockOptions & QMainWindow::AnimatedDocks && animate);
2982 isInApplyState = false;
2983}
2984
2985void QMainWindowLayout::saveState(QDataStream &stream) const
2986{
2987 layoutState.saveState(stream);
2988}
2989
2990bool QMainWindowLayout::restoreState(QDataStream &stream)
2991{
2992 QScopedValueRollback<bool> guard(isInRestoreState, true);
2993 savedState = layoutState;
2994 layoutState.clear();
2995 layoutState.rect = savedState.rect;
2996
2997 if (!layoutState.restoreState(stream&: stream, oldState: savedState)) {
2998 layoutState.deleteAllLayoutItems();
2999 layoutState = savedState;
3000 if (parentWidget()->isVisible())
3001 applyState(newState&: layoutState, animate: false); // hides tabBars allocated by newState
3002 return false;
3003 }
3004
3005 if (parentWidget()->isVisible()) {
3006 layoutState.fitLayout();
3007 applyState(newState&: layoutState, animate: false);
3008 } else {
3009 /*
3010 The state might not fit into the size of the widget as it gets shown, but
3011 if the window is expected to be maximized or full screened, then we might
3012 get several resizes as part of that transition, at the end of which the
3013 state might fit. So keep the restored state around for now and try again
3014 later in setGeometry.
3015 */
3016 if ((parentWidget()->windowState() & (Qt::WindowFullScreen | Qt::WindowMaximized))
3017 && !layoutState.fits()) {
3018 restoredState.reset(p: new QMainWindowLayoutState(layoutState));
3019 }
3020 }
3021
3022 savedState.deleteAllLayoutItems();
3023 savedState.clear();
3024
3025#if QT_CONFIG(dockwidget)
3026 if (parentWidget()->isVisible()) {
3027#if QT_CONFIG(tabbar)
3028 for (QTabBar *tab_bar : std::as_const(t&: usedTabBars))
3029 tab_bar->show();
3030
3031#endif
3032 }
3033#endif // QT_CONFIG(dockwidget)
3034
3035 return true;
3036}
3037
3038#if QT_CONFIG(draganddrop)
3039bool QMainWindowLayout::needsPlatformDrag()
3040{
3041 static const bool wayland =
3042 QGuiApplication::platformName().startsWith(s: "wayland"_L1, cs: Qt::CaseInsensitive);
3043 return wayland;
3044}
3045
3046Qt::DropAction QMainWindowLayout::performPlatformWidgetDrag(QLayoutItem *widgetItem,
3047 const QPoint &pressPosition)
3048{
3049 draggingWidget = widgetItem;
3050 QWidget *widget = widgetItem->widget();
3051 auto drag = QDrag(widget);
3052 auto mimeData = new QMimeData();
3053 auto window = widgetItem->widget()->windowHandle();
3054
3055 auto serialize = [](const auto &object) {
3056 QByteArray data;
3057 QDataStream dataStream(&data, QIODevice::WriteOnly);
3058 dataStream << object;
3059 return data;
3060 };
3061 mimeData->setData(mimetype: "application/x-qt-mainwindowdrag-window"_L1,
3062 data: serialize(reinterpret_cast<qintptr>(window)));
3063 mimeData->setData(mimetype: "application/x-qt-mainwindowdrag-position"_L1, data: serialize(pressPosition));
3064 drag.setMimeData(mimeData);
3065
3066 auto result = drag.exec();
3067
3068 draggingWidget = nullptr;
3069 return result;
3070}
3071#endif
3072
3073QT_END_NAMESPACE
3074
3075#include "moc_qmainwindowlayout_p.cpp"
3076

source code of qtbase/src/widgets/widgets/qmainwindowlayout.cpp