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

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