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