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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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