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