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