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