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