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
1252#if QT_CONFIG(dockwidget)
1253static QList<QDockWidget*> allMyDockWidgets(const QWidget *mainWindow)
1254{
1255 QList<QDockWidget*> result;
1256 for (QObject *c : mainWindow->children()) {
1257 if (auto *dw = qobject_cast<QDockWidget*>(c)) {
1258 result.append(dw);
1259 } else if (auto *gw = qobject_cast<QDockWidgetGroupWindow*>(c)) {
1260 for (QObject *c : gw->children()) {
1261 if (auto *dw = qobject_cast<QDockWidget*>(c))
1262 result.append(dw);
1263 }
1264 }
1265 }
1266
1267 return result;
1268}
1269#endif // QT_CONFIG(dockwidget)
1270
1271//pre4.3 tests the format that was used before 4.3
1272bool QMainWindowLayoutState::checkFormat(QDataStream &stream)
1273{
1274 while (!stream.atEnd()) {
1275 uchar marker;
1276 stream >> marker;
1277 switch(marker)
1278 {
1279#if QT_CONFIG(toolbar)
1280 case QToolBarAreaLayout::ToolBarStateMarker:
1281 case QToolBarAreaLayout::ToolBarStateMarkerEx:
1282 {
1283 const auto toolBars = mainWindow->findChildren<QToolBar*>();
1284 if (!toolBarAreaLayout.restoreState(stream, toolBars, marker, QInternal::Testing))
1285 return false;
1286 }
1287 break;
1288#endif // QT_CONFIG(toolbar)
1289
1290#if QT_CONFIG(dockwidget)
1291 case QDockAreaLayout::DockWidgetStateMarker:
1292 {
1293 const auto dockWidgets = allMyDockWidgets(mainWindow);
1294 if (!dockAreaLayout.restoreState(stream, dockWidgets, QInternal::Testing))
1295 return false;
1296 }
1297 break;
1298#if QT_CONFIG(tabbar)
1299 case QDockAreaLayout::FloatingDockWidgetTabMarker:
1300 {
1301 QRect geom;
1302 stream >> geom;
1303 QDockAreaLayoutInfo info;
1304 auto dockWidgets = allMyDockWidgets(mainWindow);
1305 if (!info.restoreState(stream, dockWidgets, QInternal::Testing))
1306 return false;
1307 }
1308 break;
1309#endif // QT_CONFIG(tabbar)
1310#endif // QT_CONFIG(dockwidget)
1311 default:
1312 //there was an error during the parsing
1313 return false;
1314 }// switch
1315 } //while
1316
1317 //everything went fine: it must be a pre-4.3 saved state
1318 return true;
1319}
1320
1321bool QMainWindowLayoutState::restoreState(QDataStream &_stream,
1322 const QMainWindowLayoutState &oldState)
1323{
1324 //make a copy of the data so that we can read it more than once
1325 QByteArray copy;
1326 while(!_stream.atEnd()) {
1327 int length = 1024;
1328 QByteArray ba(length, '\0');
1329 length = _stream.readRawData(ba.data(), ba.size());
1330 ba.resize(length);
1331 copy += ba;
1332 }
1333
1334 QDataStream ds(copy);
1335 ds.setVersion(_stream.version());
1336 if (!checkFormat(ds))
1337 return false;
1338
1339 QDataStream stream(copy);
1340 stream.setVersion(_stream.version());
1341
1342 while (!stream.atEnd()) {
1343 uchar marker;
1344 stream >> marker;
1345 switch(marker)
1346 {
1347#if QT_CONFIG(dockwidget)
1348 case QDockAreaLayout::DockWidgetStateMarker:
1349 {
1350 const auto dockWidgets = allMyDockWidgets(mainWindow);
1351 if (!dockAreaLayout.restoreState(stream, dockWidgets, QInternal::Live))
1352 return false;
1353
1354 for (auto *w : dockWidgets) {
1355 const QList<int> path = dockAreaLayout.indexOf(w);
1356 if (path.isEmpty()) {
1357 QList<int> oldPath = oldState.dockAreaLayout.indexOf(w);
1358 if (oldPath.isEmpty())
1359 continue;
1360 QDockAreaLayoutInfo *info = dockAreaLayout.info(oldPath);
1361 if (info == nullptr) {
1362 continue;
1363 }
1364 info->add(w);
1365 }
1366 }
1367 }
1368 break;
1369#if QT_CONFIG(tabwidget)
1370 case QDockAreaLayout::FloatingDockWidgetTabMarker:
1371 {
1372 auto dockWidgets = allMyDockWidgets(mainWindow);
1373 QDockWidgetGroupWindow* floatingTab = qt_mainwindow_layout(mainWindow)->createTabbedDockWindow();
1374 *floatingTab->layoutInfo() = QDockAreaLayoutInfo(
1375 &dockAreaLayout.sep, QInternal::LeftDock, // FIXME: DockWidget doesn't save original docking area
1376 Qt::Horizontal, QTabBar::RoundedSouth, mainWindow);
1377 QRect geometry;
1378 stream >> geometry;
1379 QDockAreaLayoutInfo *info = floatingTab->layoutInfo();
1380 if (!info->restoreState(stream, dockWidgets, QInternal::Live))
1381 return false;
1382 geometry = QDockAreaLayout::constrainedRect(geometry, floatingTab);
1383 floatingTab->move(geometry.topLeft());
1384 floatingTab->resize(geometry.size());
1385
1386 // Don't show an empty QDockWidgetGroupWindow if no dock widget is available yet.
1387 // reparentWidgets() would be triggered by show(), so do it explicitly here.
1388 if (info->onlyHasPlaceholders())
1389 info->reparentWidgets(floatingTab);
1390 else
1391 floatingTab->show();
1392 }
1393 break;
1394#endif // QT_CONFIG(tabwidget)
1395#endif // QT_CONFIG(dockwidget)
1396
1397#if QT_CONFIG(toolbar)
1398 case QToolBarAreaLayout::ToolBarStateMarker:
1399 case QToolBarAreaLayout::ToolBarStateMarkerEx:
1400 {
1401 const auto toolBars = mainWindow->findChildren<QToolBar*>();
1402 if (!toolBarAreaLayout.restoreState(stream, toolBars, marker, QInternal::Live))
1403 return false;
1404
1405 for (auto *bar : toolBars) {
1406 const QList<int> path = toolBarAreaLayout.indexOf(bar);
1407 if (path.isEmpty()) {
1408 const QList<int> oldPath = oldState.toolBarAreaLayout.indexOf(bar);
1409 if (oldPath.isEmpty())
1410 continue;
1411 toolBarAreaLayout.docks[oldPath.at(0)].insertToolBar(nullptr, bar);
1412 }
1413 }
1414 }
1415 break;
1416#endif // QT_CONFIG(toolbar)
1417 default:
1418 return false;
1419 }// switch
1420 } //while
1421
1422
1423 return true;
1424}
1425
1426/******************************************************************************
1427** QMainWindowLayoutState - toolbars
1428*/
1429
1430#if QT_CONFIG(toolbar)
1431
1432static constexpr Qt::ToolBarArea validateToolBarArea(Qt::ToolBarArea area)
1433{
1434 switch (area) {
1435 case Qt::LeftToolBarArea:
1436 case Qt::RightToolBarArea:
1437 case Qt::TopToolBarArea:
1438 case Qt::BottomToolBarArea:
1439 return area;
1440 default:
1441 break;
1442 }
1443 return Qt::TopToolBarArea;
1444}
1445
1446static QInternal::DockPosition toDockPos(Qt::ToolBarArea area)
1447{
1448 switch (area) {
1449 case Qt::LeftToolBarArea: return QInternal::LeftDock;
1450 case Qt::RightToolBarArea: return QInternal::RightDock;
1451 case Qt::TopToolBarArea: return QInternal::TopDock;
1452 case Qt::BottomToolBarArea: return QInternal::BottomDock;
1453 default:
1454 break;
1455 }
1456
1457 return QInternal::DockCount;
1458}
1459
1460static Qt::ToolBarArea toToolBarArea(QInternal::DockPosition pos)
1461{
1462 switch (pos) {
1463 case QInternal::LeftDock: return Qt::LeftToolBarArea;
1464 case QInternal::RightDock: return Qt::RightToolBarArea;
1465 case QInternal::TopDock: return Qt::TopToolBarArea;
1466 case QInternal::BottomDock: return Qt::BottomToolBarArea;
1467 default: break;
1468 }
1469 return Qt::NoToolBarArea;
1470}
1471
1472static inline Qt::ToolBarArea toToolBarArea(int pos)
1473{
1474 return toToolBarArea(static_cast<QInternal::DockPosition>(pos));
1475}
1476
1477void QMainWindowLayout::addToolBarBreak(Qt::ToolBarArea area)
1478{
1479 area = validateToolBarArea(area);
1480
1481 layoutState.toolBarAreaLayout.addToolBarBreak(toDockPos(area));
1482 if (savedState.isValid())
1483 savedState.toolBarAreaLayout.addToolBarBreak(toDockPos(area));
1484
1485 invalidate();
1486}
1487
1488void QMainWindowLayout::insertToolBarBreak(QToolBar *before)
1489{
1490 layoutState.toolBarAreaLayout.insertToolBarBreak(before);
1491 if (savedState.isValid())
1492 savedState.toolBarAreaLayout.insertToolBarBreak(before);
1493 invalidate();
1494}
1495
1496void QMainWindowLayout::removeToolBarBreak(QToolBar *before)
1497{
1498 layoutState.toolBarAreaLayout.removeToolBarBreak(before);
1499 if (savedState.isValid())
1500 savedState.toolBarAreaLayout.removeToolBarBreak(before);
1501 invalidate();
1502}
1503
1504void QMainWindowLayout::moveToolBar(QToolBar *toolbar, int pos)
1505{
1506 layoutState.toolBarAreaLayout.moveToolBar(toolbar, pos);
1507 if (savedState.isValid())
1508 savedState.toolBarAreaLayout.moveToolBar(toolbar, pos);
1509 invalidate();
1510}
1511
1512/* Removes the toolbar from the mainwindow so that it can be added again. Does not
1513 explicitly hide the toolbar. */
1514void QMainWindowLayout::removeToolBar(QToolBar *toolbar)
1515{
1516 if (toolbar) {
1517 QObject::disconnect(parentWidget(), SIGNAL(iconSizeChanged(QSize)),
1518 toolbar, SLOT(_q_updateIconSize(QSize)));
1519 QObject::disconnect(parentWidget(), SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)),
1520 toolbar, SLOT(_q_updateToolButtonStyle(Qt::ToolButtonStyle)));
1521
1522 removeWidget(toolbar);
1523 }
1524}
1525
1526/*!
1527 \class QMainWindowLayout
1528 \inmodule QtWidgets
1529 \internal
1530*/
1531
1532/*!
1533 Adds \a toolbar to \a area, continuing the current line.
1534*/
1535void QMainWindowLayout::addToolBar(Qt::ToolBarArea area,
1536 QToolBar *toolbar,
1537 bool)
1538{
1539 area = validateToolBarArea(area);
1540 // let's add the toolbar to the layout
1541 addChildWidget(toolbar);
1542 QLayoutItem *item = layoutState.toolBarAreaLayout.addToolBar(toDockPos(area), toolbar);
1543 if (savedState.isValid() && item) {
1544 // copy the toolbar also in the saved state
1545 savedState.toolBarAreaLayout.insertItem(toDockPos(area), item);
1546 }
1547 invalidate();
1548
1549 // this ensures that the toolbar has the right window flags (not floating any more)
1550 toolbar->d_func()->updateWindowFlags(false /*floating*/);
1551}
1552
1553/*!
1554 Adds \a toolbar before \a before
1555*/
1556void QMainWindowLayout::insertToolBar(QToolBar *before, QToolBar *toolbar)
1557{
1558 addChildWidget(toolbar);
1559 QLayoutItem *item = layoutState.toolBarAreaLayout.insertToolBar(before, toolbar);
1560 if (savedState.isValid() && item) {
1561 // copy the toolbar also in the saved state
1562 savedState.toolBarAreaLayout.insertItem(before, item);
1563 }
1564 if (!currentGapPos.isEmpty() && currentGapPos.constFirst() == 0) {
1565 currentGapPos = layoutState.toolBarAreaLayout.currentGapIndex();
1566 if (!currentGapPos.isEmpty()) {
1567 currentGapPos.prepend(0);
1568 currentGapRect = layoutState.itemRect(currentGapPos);
1569 }
1570 }
1571 invalidate();
1572}
1573
1574Qt::ToolBarArea QMainWindowLayout::toolBarArea(const QToolBar *toolbar) const
1575{
1576 QInternal::DockPosition pos = layoutState.toolBarAreaLayout.findToolBar(toolbar);
1577 switch (pos) {
1578 case QInternal::LeftDock: return Qt::LeftToolBarArea;
1579 case QInternal::RightDock: return Qt::RightToolBarArea;
1580 case QInternal::TopDock: return Qt::TopToolBarArea;
1581 case QInternal::BottomDock: return Qt::BottomToolBarArea;
1582 default: break;
1583 }
1584 return Qt::NoToolBarArea;
1585}
1586
1587bool QMainWindowLayout::toolBarBreak(QToolBar *toolBar) const
1588{
1589 return layoutState.toolBarAreaLayout.toolBarBreak(toolBar);
1590}
1591
1592void QMainWindowLayout::getStyleOptionInfo(QStyleOptionToolBar *option, QToolBar *toolBar) const
1593{
1594 option->toolBarArea = toolBarArea(toolBar);
1595 layoutState.toolBarAreaLayout.getStyleOptionInfo(option, toolBar);
1596}
1597
1598void QMainWindowLayout::toggleToolBarsVisible()
1599{
1600 layoutState.toolBarAreaLayout.visible = !layoutState.toolBarAreaLayout.visible;
1601 if (!layoutState.mainWindow->isMaximized()) {
1602 QPoint topLeft = parentWidget()->geometry().topLeft();
1603 QRect r = parentWidget()->geometry();
1604 r = layoutState.toolBarAreaLayout.rectHint(r);
1605 r.moveTo(topLeft);
1606 parentWidget()->setGeometry(r);
1607 } else {
1608 update();
1609 }
1610}
1611
1612#endif // QT_CONFIG(toolbar)
1613
1614/******************************************************************************
1615** QMainWindowLayoutState - dock areas
1616*/
1617
1618#if QT_CONFIG(dockwidget)
1619
1620static QInternal::DockPosition toDockPos(Qt::DockWidgetArea area)
1621{
1622 switch (area) {
1623 case Qt::LeftDockWidgetArea: return QInternal::LeftDock;
1624 case Qt::RightDockWidgetArea: return QInternal::RightDock;
1625 case Qt::TopDockWidgetArea: return QInternal::TopDock;
1626 case Qt::BottomDockWidgetArea: return QInternal::BottomDock;
1627 default:
1628 break;
1629 }
1630
1631 return QInternal::DockCount;
1632}
1633
1634inline static Qt::DockWidgetArea toDockWidgetArea(int pos)
1635{
1636 return QDockWidgetPrivate::toDockWidgetArea(static_cast<QInternal::DockPosition>(pos));
1637}
1638
1639// Checks if QDockWidgetGroupWindow or QDockWidget can be plugged the area indicated by path.
1640// Returns false if called with invalid widget type or if compiled without dockwidget support.
1641static bool isAreaAllowed(QWidget *widget, const QList<int> &path)
1642{
1643 Q_ASSERT_X((path.size() > 1), "isAreaAllowed", "invalid path size");
1644 const Qt::DockWidgetArea area = toDockWidgetArea(path[1]);
1645
1646 // Read permissions directly from a single dock widget
1647 if (QDockWidget *dw = qobject_cast<QDockWidget *>(widget)) {
1648 const bool allowed = dw->isAreaAllowed(area);
1649 if (!allowed)
1650 qCDebug(lcQpaDockWidgets) << "No permission for single DockWidget" << widget << "to dock on" << area;
1651 return allowed;
1652 }
1653
1654 // Read permissions from a DockWidgetGroupWindow depending on its DockWidget children
1655 if (QDockWidgetGroupWindow *dwgw = qobject_cast<QDockWidgetGroupWindow *>(widget)) {
1656 const QList<QDockWidget *> children = dwgw->findChildren<QDockWidget *>(QString(), Qt::FindDirectChildrenOnly);
1657
1658 if (children.size() == 1) {
1659 // Group window has a single child => read its permissions
1660 const bool allowed = children.at(0)->isAreaAllowed(area);
1661 if (!allowed)
1662 qCDebug(lcQpaDockWidgets) << "No permission for DockWidgetGroupWindow" << widget << "to dock on" << area;
1663 return allowed;
1664 } else {
1665 // Group window has more than one or no children => dock it anywhere
1666 qCDebug(lcQpaDockWidgets) << "DockWidgetGroupWindow" << widget << "has" << children.size() << "children:";
1667 qCDebug(lcQpaDockWidgets) << children;
1668 qCDebug(lcQpaDockWidgets) << "DockWidgetGroupWindow" << widget << "can dock at" << area << "and anywhere else.";
1669 return true;
1670 }
1671 }
1672 qCDebug(lcQpaDockWidgets) << "Docking requested for invalid widget type (coding error)." << widget << area;
1673 return false;
1674}
1675
1676void QMainWindowLayout::setCorner(Qt::Corner corner, Qt::DockWidgetArea area)
1677{
1678 if (layoutState.dockAreaLayout.corners[corner] == area)
1679 return;
1680 layoutState.dockAreaLayout.corners[corner] = area;
1681 if (savedState.isValid())
1682 savedState.dockAreaLayout.corners[corner] = area;
1683 invalidate();
1684}
1685
1686Qt::DockWidgetArea QMainWindowLayout::corner(Qt::Corner corner) const
1687{
1688 return layoutState.dockAreaLayout.corners[corner];
1689}
1690
1691// Returns the rectangle of a dockWidgetArea
1692// if max is true, the maximum possible rectangle for dropping is returned
1693// the current visible rectangle otherwise
1694QRect QMainWindowLayout::dockWidgetAreaRect(const Qt::DockWidgetArea area, DockWidgetAreaSize size) const
1695{
1696 const QInternal::DockPosition dockPosition = toDockPos(area);
1697
1698 // Called with invalid dock widget area
1699 if (dockPosition == QInternal::DockCount) {
1700 qCDebug(lcQpaDockWidgets) << "QMainWindowLayout::dockWidgetAreaRect called with" << area;
1701 return QRect();
1702 }
1703
1704 const QDockAreaLayout dl = layoutState.dockAreaLayout;
1705
1706 // Return maximum or visible rectangle
1707 return (size == Maximum) ? dl.gapRect(dockPosition) : dl.docks[dockPosition].rect;
1708}
1709
1710/*!
1711 \internal
1712 Add \a dockwidget to \a area in \a orientation.
1713 */
1714void QMainWindowLayout::addDockWidget(Qt::DockWidgetArea area,
1715 QDockWidget *dockwidget,
1716 Qt::Orientation orientation)
1717{
1718 addChildWidget(dockwidget);
1719
1720 // If we are currently moving a separator, then we need to abort the move, since each
1721 // time we move the mouse layoutState is replaced by savedState modified by the move.
1722 if (!movingSeparator.isEmpty())
1723 endSeparatorMove(movingSeparatorPos);
1724
1725 layoutState.dockAreaLayout.addDockWidget(toDockPos(area), dockwidget, orientation);
1726 invalidate();
1727}
1728
1729bool QMainWindowLayout::restoreDockWidget(QDockWidget *dockwidget)
1730{
1731 addChildWidget(dockwidget);
1732 if (!layoutState.dockAreaLayout.restoreDockWidget(dockwidget))
1733 return false;
1734 emit dockwidget->dockLocationChanged(dockWidgetArea(dockwidget));
1735 invalidate();
1736 return true;
1737}
1738
1739#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
1740static QTabBar::Shape tabwidgetPositionToTabBarShape(QDockWidget *w)
1741{
1742 switch (static_cast<QDockWidgetPrivate *>(qt_widget_private(w))->tabPosition) {
1743 case QTabWidget::North:
1744 return QTabBar::RoundedNorth;
1745 case QTabWidget::South:
1746 return QTabBar::RoundedSouth;
1747 case QTabWidget::West:
1748 return QTabBar::RoundedWest;
1749 case QTabWidget::East:
1750 return QTabBar::RoundedEast;
1751 }
1752 Q_UNREACHABLE_RETURN(QTabBar::RoundedSouth);
1753}
1754#endif // QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
1755
1756#if QT_CONFIG(tabbar)
1757void QMainWindowLayout::tabifyDockWidget(QDockWidget *first, QDockWidget *second)
1758{
1759 Q_ASSERT(first);
1760 Q_ASSERT(second);
1761
1762 // first must have been added to the main window, otherwise we have no dock location
1763 if (layoutState.dockAreaLayout.indexOf(first).isEmpty()) {
1764 qCritical() << "Coding error: QDockWidget" << first
1765 << "tabbed before QMainWindow::addDockWidget()."
1766 << "Ignoring" << __FUNCTION__;
1767 return;
1768 }
1769
1770 // if second hasn't been added to the main window, add it at first's location
1771 if (layoutState.dockAreaLayout.indexOf(second).isEmpty()) {
1772 layoutState.mainWindow->addDockWidget(first->dockLocation(), second);
1773 qCDebug(lcQpaDockWidgets) << "QDockWidget" << second << "has been added to"
1774 << parent() << "at" << first->dockLocation();
1775 }
1776
1777 // Do not interfer with existing floating tabs.
1778 if (isDockWidgetTabbed(second)) {
1779 qCDebug(lcQpaDockWidgets) << "QDockWidget" << second
1780 << "is already tabbed. Ignoring" << __FUNCTION__;
1781 return;
1782 }
1783
1784 const auto oldLocationFirst = dockWidgetArea(first);
1785 if (first->isFloating()) {
1786 tabifyWhileFloating(first, second);
1787 } else {
1788 applyRestoredState();
1789 addChildWidget(second);
1790 layoutState.dockAreaLayout.tabifyDockWidget(first, second);
1791 }
1792 const auto newLocationFirst = dockWidgetArea(first);
1793 if (newLocationFirst != oldLocationFirst)
1794 emit second->dockLocationChanged(newLocationFirst);
1795 invalidate();
1796}
1797
1798void QMainWindowLayout::tabifyWhileFloating(QDockWidget *first, QDockWidget *second)
1799{
1800 Q_ASSERT(first->isFloating());
1801 Q_ASSERT(!isDockWidgetTabbed(first));
1802 Q_ASSERT(!isDockWidgetTabbed(second));
1803
1804 // if the second dock widget is still docked, make it floating
1805 second->setFloating(true);
1806
1807 QDockWidgetGroupWindow *floatingTabs = createTabbedDockWindow();
1808 floatingTabs->setGeometry(first->geometry());
1809 QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
1810 const QTabBar::Shape shape = tabwidgetPositionToTabBarShape(first);
1811
1812 const QInternal::DockPosition dockPosition = toDockPos(dockWidgetArea(first));
1813 Q_ASSERT(dockPosition != QInternal::DockPosition::DockCount);
1814 *info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, dockPosition,
1815 Qt::Horizontal, shape,
1816 static_cast<QMainWindow *>(parentWidget()));
1817 info->tabBar = getTabBar();
1818 info->tabbed = true;
1819 info->add(first);
1820 info->add(second);
1821 second->d_func()->plug(first->geometry());
1822 QDockAreaLayoutInfo &parentInfo = layoutState.dockAreaLayout.docks[dockPosition];
1823 parentInfo.add(floatingTabs);
1824 first->setParent(floatingTabs);
1825 second->setParent(floatingTabs);
1826 floatingTabs->show();
1827 floatingTabs->raise();
1828}
1829
1830bool QMainWindowLayout::documentMode() const
1831{
1832 return _documentMode;
1833}
1834
1835void QMainWindowLayout::setDocumentMode(bool enabled)
1836{
1837 if (_documentMode == enabled)
1838 return;
1839
1840 _documentMode = enabled;
1841
1842 // Update the document mode for all tab bars
1843 for (QTabBar *bar : std::as_const(usedTabBars))
1844 bar->setDocumentMode(_documentMode);
1845}
1846
1847void QMainWindowLayout::setVerticalTabsEnabled(bool enabled)
1848{
1849 if (verticalTabsEnabled == enabled)
1850 return;
1851
1852 verticalTabsEnabled = enabled;
1853
1854 updateTabBarShapes();
1855}
1856
1857#if QT_CONFIG(tabwidget)
1858QTabWidget::TabShape QMainWindowLayout::tabShape() const
1859{
1860 return _tabShape;
1861}
1862
1863void QMainWindowLayout::setTabShape(QTabWidget::TabShape tabShape)
1864{
1865 if (_tabShape == tabShape)
1866 return;
1867
1868 _tabShape = tabShape;
1869
1870 updateTabBarShapes();
1871}
1872
1873QTabWidget::TabPosition QMainWindowLayout::tabPosition(Qt::DockWidgetArea area) const
1874{
1875 const QInternal::DockPosition dockPos = toDockPos(area);
1876 if (dockPos < QInternal::DockCount)
1877 return tabPositions[dockPos];
1878 qWarning("QMainWindowLayout::tabPosition called with out-of-bounds value '%d'", int(area));
1879 return QTabWidget::North;
1880}
1881
1882void QMainWindowLayout::setTabPosition(Qt::DockWidgetAreas areas, QTabWidget::TabPosition tabPosition)
1883{
1884 static constexpr Qt::DockWidgetArea dockWidgetAreas[] = {
1885 Qt::TopDockWidgetArea,
1886 Qt::LeftDockWidgetArea,
1887 Qt::BottomDockWidgetArea,
1888 Qt::RightDockWidgetArea
1889 };
1890 static constexpr QInternal::DockPosition dockPositions[] = {
1891 QInternal::TopDock,
1892 QInternal::LeftDock,
1893 QInternal::BottomDock,
1894 QInternal::RightDock
1895 };
1896
1897 for (int i = 0; i < QInternal::DockCount; ++i)
1898 if (areas & dockWidgetAreas[i])
1899 tabPositions[dockPositions[i]] = tabPosition;
1900
1901 updateTabBarShapes();
1902}
1903
1904QTabBar::Shape _q_tb_tabBarShapeFrom(QTabWidget::TabShape shape, QTabWidget::TabPosition position);
1905#endif // QT_CONFIG(tabwidget)
1906
1907void QMainWindowLayout::showTabBars()
1908{
1909 const auto usedTabBarsCopy = usedTabBars; // list potentially modified by animations
1910 for (QTabBar *tab_bar : usedTabBarsCopy) {
1911 if (usedTabBars.contains(tab_bar)) // Showing a tab bar can cause another to be deleted.
1912 tab_bar->show();
1913 }
1914}
1915
1916void QMainWindowLayout::updateTabBarShapes()
1917{
1918#if QT_CONFIG(tabwidget)
1919 static constexpr QTabWidget::TabPosition vertical[] = {
1920 QTabWidget::West,
1921 QTabWidget::East,
1922 QTabWidget::North,
1923 QTabWidget::South
1924 };
1925#else
1926 static constexpr QTabBar::Shape vertical[] = {
1927 QTabBar::RoundedWest,
1928 QTabBar::RoundedEast,
1929 QTabBar::RoundedNorth,
1930 QTabBar::RoundedSouth
1931 };
1932#endif
1933
1934 QDockAreaLayout &layout = layoutState.dockAreaLayout;
1935
1936 for (int i = 0; i < QInternal::DockCount; ++i) {
1937#if QT_CONFIG(tabwidget)
1938 QTabWidget::TabPosition pos = verticalTabsEnabled ? vertical[i] : tabPositions[i];
1939 QTabBar::Shape shape = _q_tb_tabBarShapeFrom(_tabShape, pos);
1940#else
1941 QTabBar::Shape shape = verticalTabsEnabled ? vertical[i] : QTabBar::RoundedSouth;
1942#endif
1943 layout.docks[i].setTabBarShape(shape);
1944 }
1945}
1946#endif // QT_CONFIG(tabbar)
1947
1948void QMainWindowLayout::splitDockWidget(QDockWidget *after,
1949 QDockWidget *dockwidget,
1950 Qt::Orientation orientation)
1951{
1952 applyRestoredState();
1953 addChildWidget(dockwidget);
1954 layoutState.dockAreaLayout.splitDockWidget(after, dockwidget, orientation);
1955 emit dockwidget->dockLocationChanged(dockWidgetArea(after));
1956 invalidate();
1957}
1958
1959Qt::DockWidgetArea QMainWindowLayout::dockWidgetArea(const QWidget *widget) const
1960{
1961 const QList<int> pathToWidget = layoutState.dockAreaLayout.indexOf(widget);
1962 if (pathToWidget.isEmpty())
1963 return Qt::NoDockWidgetArea;
1964 return toDockWidgetArea(pathToWidget.first());
1965}
1966
1967void QMainWindowLayout::keepSize(QDockWidget *w)
1968{
1969 layoutState.dockAreaLayout.keepSize(w);
1970}
1971
1972#if QT_CONFIG(tabbar)
1973
1974// Handle custom tooltip, and allow to drag tabs away.
1975class QMainWindowTabBar : public QTabBar
1976{
1977 Q_OBJECT
1978 QPointer<QMainWindow> mainWindow;
1979 QPointer<QDockWidget> draggingDock; // Currently dragging (detached) dock widget
1980public:
1981 QMainWindowTabBar(QMainWindow *parent);
1982 ~QMainWindowTabBar();
1983 QDockWidget *dockAt(int index) const;
1984 QList<QDockWidget *> dockWidgets() const;
1985 bool contains(const QDockWidget *dockWidget) const;
1986protected:
1987 bool event(QEvent *e) override;
1988 void mouseReleaseEvent(QMouseEvent*) override;
1989 void mouseMoveEvent(QMouseEvent*) override;
1990
1991};
1992
1993QDebug operator<<(QDebug debug, const QMainWindowTabBar *bar)
1994{
1995 if (!bar)
1996 return debug << "QMainWindowTabBar(0x0)";
1997 QDebugStateSaver saver(debug);
1998 debug.nospace().noquote() << "QMainWindowTabBar(" << static_cast<const void *>(bar) << ", ";
1999 debug.nospace().noquote() << "ParentWidget=(" << bar->parentWidget() << "), ";
2000 const auto dockWidgets = bar->dockWidgets();
2001 if (dockWidgets.isEmpty())
2002 debug.nospace().noquote() << "No QDockWidgets";
2003 else
2004 debug.nospace().noquote() << "DockWidgets(" << dockWidgets << ")";
2005 debug.nospace().noquote() << ")";
2006 return debug;
2007}
2008
2009QMainWindowTabBar *QMainWindowLayout::findTabBar(const QDockWidget *dockWidget) const
2010{
2011 for (auto *bar : usedTabBars) {
2012 Q_ASSERT(qobject_cast<QMainWindowTabBar *>(bar));
2013 auto *tabBar = static_cast<QMainWindowTabBar *>(bar);
2014 if (tabBar->contains(dockWidget))
2015 return tabBar;
2016 }
2017 return nullptr;
2018}
2019
2020QMainWindowTabBar::QMainWindowTabBar(QMainWindow *parent)
2021 : QTabBar(parent), mainWindow(parent)
2022{
2023 setExpanding(false);
2024}
2025
2026QList<QDockWidget *> QMainWindowTabBar::dockWidgets() const
2027{
2028 QList<QDockWidget *> docks;
2029 for (int i = 0; i < count(); ++i) {
2030 if (QDockWidget *dock = dockAt(i))
2031 docks << dock;
2032 }
2033 return docks;
2034}
2035
2036bool QMainWindowTabBar::contains(const QDockWidget *dockWidget) const
2037{
2038 for (int i = 0; i < count(); ++i) {
2039 if (dockAt(i) == dockWidget)
2040 return true;
2041 }
2042 return false;
2043}
2044
2045// When a dock widget is removed from a floating tab,
2046// Events need to be processed for the tab bar to realize that the dock widget is gone.
2047// In this case count() counts the dock widget in transition and accesses dockAt
2048// with an out-of-bounds index.
2049// => return nullptr in contrast to other xxxxxAt() functions
2050QDockWidget *QMainWindowTabBar::dockAt(int index) const
2051{
2052 QMainWindowTabBar *that = const_cast<QMainWindowTabBar *>(this);
2053 QMainWindowLayout* mlayout = qt_mainwindow_layout(mainWindow);
2054 QDockAreaLayoutInfo *info = mlayout ? mlayout->dockInfo(that) : nullptr;
2055 if (!info)
2056 return nullptr;
2057
2058 const int itemIndex = info->tabIndexToListIndex(index);
2059 if (itemIndex >= 0) {
2060 Q_ASSERT(itemIndex < info->item_list.count());
2061 const QDockAreaLayoutItem &item = info->item_list.at(itemIndex);
2062 return item.widgetItem ? qobject_cast<QDockWidget *>(item.widgetItem->widget()) : nullptr;
2063 }
2064
2065 return nullptr;
2066}
2067
2068/*!
2069 \internal
2070 Move \a dockWidget to its ideal unplug position.
2071 \list
2072 \li If \a dockWidget has a title bar widget, place its center under the mouse cursor.
2073 \li Otherwise place it in the middle of the title bar's long side, with a
2074 QApplication::startDragDistance() offset on the short side.
2075 \endlist
2076 */
2077static void moveToUnplugPosition(QPoint mouse, QDockWidget *dockWidget)
2078{
2079 Q_ASSERT(dockWidget);
2080
2081 if (auto *tbWidget = dockWidget->titleBarWidget()) {
2082 dockWidget->move(mouse - tbWidget->rect().center());
2083 return;
2084 }
2085
2086 const bool vertical = dockWidget->features().testFlag(QDockWidget::DockWidgetVerticalTitleBar);
2087 const int deltaX = vertical ? QApplication::startDragDistance() : dockWidget->width() / 2;
2088 const int deltaY = vertical ? dockWidget->height() / 2 : QApplication::startDragDistance();
2089 dockWidget->move(mouse - QPoint(deltaX, deltaY));
2090}
2091
2092void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e)
2093{
2094 // The QTabBar handles the moving (reordering) of tabs.
2095 // When QTabBarPrivate::dragInProgress is true, and that the mouse is outside of a region
2096 // around the QTabBar, we will consider the user wants to drag that QDockWidget away from this
2097 // tab area.
2098
2099 QTabBarPrivate *d = static_cast<QTabBarPrivate*>(d_ptr.data());
2100 if (!draggingDock && (mainWindow->dockOptions() & QMainWindow::GroupedDragging)) {
2101 int offset = QApplication::startDragDistance() + 1;
2102 offset *= 3;
2103 QRect r = rect().adjusted(-offset, -offset, offset, offset);
2104 if (d->dragInProgress && !r.contains(e->position().toPoint()) && d->validIndex(d->pressedIndex)) {
2105 draggingDock = dockAt(d->pressedIndex);
2106 if (draggingDock) {
2107 // We should drag this QDockWidget away by unpluging it.
2108 // First cancel the QTabBar's internal move
2109 d->moveTabFinished(d->pressedIndex);
2110 d->pressedIndex = -1;
2111 if (d->movingTab)
2112 d->movingTab->setVisible(false);
2113 d->dragStartPosition = QPoint();
2114
2115 // Then starts the drag using QDockWidgetPrivate's API
2116 QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock));
2117 QDockWidgetLayout *dwlayout = static_cast<QDockWidgetLayout *>(draggingDock->layout());
2118 dockPriv->initDrag(dwlayout->titleArea().center(), true);
2119 dockPriv->startDrag(QDockWidgetPrivate::DragScope::Widget);
2120 if (dockPriv->state)
2121 dockPriv->state->ctrlDrag = e->modifiers() & Qt::ControlModifier;
2122 }
2123 }
2124 }
2125
2126 if (draggingDock) {
2127 QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock));
2128 if (dockPriv->state && dockPriv->state->dragging) {
2129 // move will call QMainWindowLayout::hover
2130 moveToUnplugPosition(e->globalPosition().toPoint(), draggingDock);
2131 }
2132 }
2133 QTabBar::mouseMoveEvent(e);
2134}
2135
2136QMainWindowTabBar::~QMainWindowTabBar()
2137{
2138 // Use qobject_cast to verify that we are not already in the (QWidget)
2139 // destructor of mainWindow
2140 if (!qobject_cast<QMainWindow *>(mainWindow) || mainWindow == parentWidget())
2141 return;
2142
2143 // tab bar is not parented to the main window
2144 // => can only be a dock widget group window
2145 auto *mwLayout = qt_mainwindow_layout(mainWindow);
2146 if (!mwLayout)
2147 return;
2148 mwLayout->usedTabBars.remove(this);
2149}
2150
2151void QMainWindowTabBar::mouseReleaseEvent(QMouseEvent *e)
2152{
2153 if (draggingDock && e->button() == Qt::LeftButton) {
2154 QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock));
2155 if (dockPriv->state && dockPriv->state->dragging)
2156 dockPriv->endDrag(QDockWidgetPrivate::EndDragMode::LocationChange);
2157
2158 draggingDock = nullptr;
2159 }
2160 QTabBar::mouseReleaseEvent(e);
2161}
2162
2163bool QMainWindowTabBar::event(QEvent *e)
2164{
2165 // show the tooltip if tab is too small to fit label
2166
2167 if (e->type() != QEvent::ToolTip)
2168 return QTabBar::event(e);
2169 QSize size = this->size();
2170 QSize hint = sizeHint();
2171 if (shape() == QTabBar::RoundedWest || shape() == QTabBar::RoundedEast) {
2172 size = size.transposed();
2173 hint = hint.transposed();
2174 }
2175 if (size.width() < hint.width())
2176 return QTabBar::event(e);
2177 e->accept();
2178 return true;
2179}
2180
2181QList<QDockWidget *> QMainWindowLayout::tabifiedDockWidgets(const QDockWidget *dockWidget) const
2182{
2183 const auto *bar = findTabBar(dockWidget);
2184 if (!bar)
2185 return {};
2186
2187 QList<QDockWidget *> buddies = bar->dockWidgets();
2188 // Return only other dock widgets associated with dockWidget in a tab bar.
2189 // If dockWidget is alone in a tab bar, return an empty list.
2190 buddies.removeOne(dockWidget);
2191 return buddies;
2192}
2193
2194bool QMainWindowLayout::isDockWidgetTabbed(const QDockWidget *dockWidget) const
2195{
2196 // A single dock widget in a tab bar is not considered to be tabbed.
2197 // This is to make sure, we don't drag an empty QDockWidgetGroupWindow around.
2198 // => only consider tab bars with two or more tabs.
2199 const auto *bar = findTabBar(dockWidget);
2200 return bar && bar->count() > 1;
2201}
2202
2203void QMainWindowLayout::unuseTabBar(QTabBar *bar)
2204{
2205 Q_ASSERT(qobject_cast<QMainWindowTabBar *>(bar));
2206 bar->deleteLater();
2207}
2208
2209QTabBar *QMainWindowLayout::getTabBar()
2210{
2211 if (!usedTabBars.isEmpty() && !isInRestoreState) {
2212 /*
2213 If dock widgets have been removed and added while the main window was
2214 hidden, then the layout hasn't been activated yet, and tab bars from empty
2215 docking areas haven't been put in the cache yet.
2216 */
2217 activate();
2218 }
2219
2220 QTabBar *bar = new QMainWindowTabBar(static_cast<QMainWindow *>(parentWidget()));
2221 bar->setDrawBase(true);
2222 bar->setElideMode(Qt::ElideRight);
2223 bar->setDocumentMode(_documentMode);
2224 bar->setMovable(true);
2225 connect(bar, SIGNAL(currentChanged(int)), this, SLOT(tabChanged()));
2226 connect(bar, &QTabBar::tabMoved, this, &QMainWindowLayout::tabMoved);
2227
2228 usedTabBars.insert(bar);
2229 return bar;
2230}
2231
2232QWidget *QMainWindowLayout::getSeparatorWidget()
2233{
2234 auto *separator = new QWidget(parentWidget());
2235 separator->setAttribute(Qt::WA_MouseNoMask, true);
2236 separator->setAutoFillBackground(false);
2237 separator->setObjectName("qt_qmainwindow_extended_splitter"_L1);
2238 usedSeparatorWidgets.insert(separator);
2239 return separator;
2240}
2241
2242/*! \internal
2243 Returns a pointer QDockAreaLayoutInfo which contains this \a widget directly
2244 (in its internal list)
2245 */
2246QDockAreaLayoutInfo *QMainWindowLayout::dockInfo(QWidget *widget)
2247{
2248 QDockAreaLayoutInfo *info = layoutState.dockAreaLayout.info(widget);
2249 if (info)
2250 return info;
2251 const auto groups =
2252 parent()->findChildren<QDockWidgetGroupWindow*>(Qt::FindDirectChildrenOnly);
2253 for (QDockWidgetGroupWindow *dwgw : groups) {
2254 info = dwgw->layoutInfo()->info(widget);
2255 if (info)
2256 return info;
2257 }
2258 return nullptr;
2259}
2260
2261void QMainWindowLayout::tabChanged()
2262{
2263 QTabBar *tb = qobject_cast<QTabBar*>(sender());
2264 if (tb == nullptr)
2265 return;
2266 QDockAreaLayoutInfo *info = dockInfo(tb);
2267 if (info == nullptr)
2268 return;
2269
2270 QDockWidget *activated = info->apply(false);
2271
2272 if (activated)
2273 emit static_cast<QMainWindow *>(parentWidget())->tabifiedDockWidgetActivated(activated);
2274
2275 if (auto dwgw = qobject_cast<QDockWidgetGroupWindow*>(tb->parentWidget()))
2276 dwgw->adjustFlags();
2277
2278 if (QWidget *w = centralWidget())
2279 w->raise();
2280}
2281
2282void QMainWindowLayout::tabMoved(int from, int to)
2283{
2284 QTabBar *tb = qobject_cast<QTabBar*>(sender());
2285 Q_ASSERT(tb);
2286 QDockAreaLayoutInfo *info = dockInfo(tb);
2287 Q_ASSERT(info);
2288
2289 info->moveTab(from, to);
2290}
2291
2292void QMainWindowLayout::raise(QDockWidget *widget)
2293{
2294 QDockAreaLayoutInfo *info = dockInfo(widget);
2295 if (info == nullptr)
2296 return;
2297 if (!info->tabbed)
2298 return;
2299 info->setCurrentTab(widget);
2300}
2301#endif // QT_CONFIG(tabbar)
2302
2303#endif // QT_CONFIG(dockwidget)
2304
2305
2306/******************************************************************************
2307** QMainWindowLayoutState - layout interface
2308*/
2309
2310int QMainWindowLayout::count() const
2311{
2312 int result = 0;
2313 while (itemAt(result))
2314 ++result;
2315 return result;
2316}
2317
2318QLayoutItem *QMainWindowLayout::itemAt(int index) const
2319{
2320 int x = 0;
2321
2322 if (QLayoutItem *ret = layoutState.itemAt(index, &x))
2323 return ret;
2324
2325 if (statusbar && x++ == index)
2326 return statusbar;
2327
2328 return nullptr;
2329}
2330
2331QLayoutItem *QMainWindowLayout::takeAt(int index)
2332{
2333 int x = 0;
2334
2335 if (QLayoutItem *ret = layoutState.takeAt(index, &x)) {
2336 // the widget might in fact have been destroyed by now
2337 if (QWidget *w = ret->widget()) {
2338 widgetAnimator.abort(w);
2339 if (w == pluggingWidget)
2340 pluggingWidget = nullptr;
2341 }
2342
2343 if (savedState.isValid() ) {
2344 //we need to remove the item also from the saved state to prevent crash
2345 savedState.remove(ret);
2346 //Also, the item may be contained several times as a gap item.
2347 layoutState.remove(ret);
2348 }
2349
2350#if QT_CONFIG(toolbar)
2351 if (!currentGapPos.isEmpty() && currentGapPos.constFirst() == 0) {
2352 currentGapPos = layoutState.toolBarAreaLayout.currentGapIndex();
2353 if (!currentGapPos.isEmpty()) {
2354 currentGapPos.prepend(0);
2355 currentGapRect = layoutState.itemRect(currentGapPos);
2356 }
2357 }
2358#endif
2359
2360 return ret;
2361 }
2362
2363 if (statusbar && x++ == index) {
2364 QLayoutItem *ret = statusbar;
2365 statusbar = nullptr;
2366 return ret;
2367 }
2368
2369 return nullptr;
2370}
2371
2372
2373/*!
2374 \internal
2375
2376 restoredState stores what we earlier read from storage, but it couldn't
2377 be applied as the mainwindow wasn't large enough (yet) to fit the state.
2378 Usually, the restored state would be applied lazily in setGeometry below.
2379 However, if the mainwindow's layout is modified (e.g. by a call to tabify or
2380 splitDockWidgets), then we have to forget the restored state as it might contain
2381 dangling pointers (QDockWidgetLayoutItem has a copy constructor that copies the
2382 layout item pointer, and splitting or tabify might have to delete some of those
2383 layout structures).
2384
2385 Functions that might result in the QMainWindowLayoutState storing dangling pointers
2386 have to call this function first, so that the restoredState becomes the actual state
2387 first, and is forgotten afterwards.
2388*/
2389void QMainWindowLayout::applyRestoredState()
2390{
2391 if (restoredState) {
2392 layoutState = *restoredState;
2393 restoredState.reset();
2394 discardRestoredStateTimer.stop();
2395 }
2396}
2397
2398void QMainWindowLayout::setGeometry(const QRect &_r)
2399{
2400 // Check if the state is valid, and avoid replacing it again if it is currently used
2401 // in applyState
2402 if (savedState.isValid() || (restoredState && isInApplyState))
2403 return;
2404
2405 QRect r = _r;
2406
2407 QLayout::setGeometry(r);
2408
2409 if (statusbar) {
2410 QRect sbr(QPoint(r.left(), 0),
2411 QSize(r.width(), statusbar->heightForWidth(r.width()))
2412 .expandedTo(statusbar->minimumSize()));
2413 sbr.moveBottom(r.bottom());
2414 QRect vr = QStyle::visualRect(parentWidget()->layoutDirection(), _r, sbr);
2415 statusbar->setGeometry(vr);
2416 r.setBottom(sbr.top() - 1);
2417 }
2418
2419 if (restoredState) {
2420 /*
2421 The main window was hidden and was going to be maximized or full-screened when
2422 the state was restored. The state might have been for a larger window size than
2423 the current size (in _r), and the window might still be in the process of being
2424 shown and transitioning to the final size (there's no reliable way of knowing
2425 this across different platforms). Try again with the restored state.
2426 */
2427 layoutState = *restoredState;
2428 if (restoredState->fits()) {
2429 restoredState.reset();
2430 discardRestoredStateTimer.stop();
2431 } else {
2432 /*
2433 Try again in the next setGeometry call, but discard the restored state
2434 after 150ms without any further tries. That's a reasonably short amount of
2435 time during which we can expect the windowing system to either have completed
2436 showing the window, or resized the window once more (which then restarts the
2437 timer in timerEvent).
2438 If the windowing system is done, then the user won't have had a chance to
2439 change the layout interactively AND trigger another resize.
2440 */
2441 discardRestoredStateTimer.start(150, this);
2442 }
2443 }
2444
2445 layoutState.rect = r;
2446
2447 layoutState.fitLayout();
2448 applyState(layoutState, false);
2449}
2450
2451void QMainWindowLayout::timerEvent(QTimerEvent *e)
2452{
2453 if (e->timerId() == discardRestoredStateTimer.timerId()) {
2454 discardRestoredStateTimer.stop();
2455 restoredState.reset();
2456 }
2457 QLayout::timerEvent(e);
2458}
2459
2460void QMainWindowLayout::addItem(QLayoutItem *)
2461{ qWarning("QMainWindowLayout::addItem: Please use the public QMainWindow API instead"); }
2462
2463QSize QMainWindowLayout::sizeHint() const
2464{
2465 if (!szHint.isValid()) {
2466 szHint = layoutState.sizeHint();
2467 const QSize sbHint = statusbar ? statusbar->sizeHint() : QSize(0, 0);
2468 szHint = QSize(qMax(sbHint.width(), szHint.width()),
2469 sbHint.height() + szHint.height());
2470 }
2471 return szHint;
2472}
2473
2474QSize QMainWindowLayout::minimumSize() const
2475{
2476 if (!minSize.isValid()) {
2477 minSize = layoutState.minimumSize();
2478 const QSize sbMin = statusbar ? statusbar->minimumSize() : QSize(0, 0);
2479 minSize = QSize(qMax(sbMin.width(), minSize.width()),
2480 sbMin.height() + minSize.height());
2481 }
2482 return minSize;
2483}
2484
2485void QMainWindowLayout::invalidate()
2486{
2487 QLayout::invalidate();
2488 minSize = szHint = QSize();
2489}
2490
2491#if QT_CONFIG(dockwidget)
2492void QMainWindowLayout::setCurrentHoveredFloat(QDockWidgetGroupWindow *w)
2493{
2494 if (currentHoveredFloat != w) {
2495 if (currentHoveredFloat) {
2496 disconnect(currentHoveredFloat.data(), &QObject::destroyed,
2497 this, &QMainWindowLayout::updateGapIndicator);
2498 disconnect(currentHoveredFloat.data(), &QDockWidgetGroupWindow::resized,
2499 this, &QMainWindowLayout::updateGapIndicator);
2500 if (currentHoveredFloat)
2501 currentHoveredFloat->restore();
2502 } else if (w) {
2503 restore(QInternal::KeepSavedState);
2504 }
2505
2506 currentHoveredFloat = w;
2507
2508 if (w) {
2509 connect(w, &QObject::destroyed,
2510 this, &QMainWindowLayout::updateGapIndicator, Qt::UniqueConnection);
2511 connect(w, &QDockWidgetGroupWindow::resized,
2512 this, &QMainWindowLayout::updateGapIndicator, Qt::UniqueConnection);
2513 }
2514
2515 updateGapIndicator();
2516 }
2517}
2518#endif // QT_CONFIG(dockwidget)
2519
2520/******************************************************************************
2521** QMainWindowLayout - remaining stuff
2522*/
2523
2524static void fixToolBarOrientation(QLayoutItem *item, int dockPos)
2525{
2526#if QT_CONFIG(toolbar)
2527 QToolBar *toolBar = qobject_cast<QToolBar*>(item->widget());
2528 if (toolBar == nullptr)
2529 return;
2530
2531 QRect oldGeo = toolBar->geometry();
2532
2533 QInternal::DockPosition pos
2534 = static_cast<QInternal::DockPosition>(dockPos);
2535 Qt::Orientation o = pos == QInternal::TopDock || pos == QInternal::BottomDock
2536 ? Qt::Horizontal : Qt::Vertical;
2537 if (o != toolBar->orientation())
2538 toolBar->setOrientation(o);
2539
2540 QSize hint = toolBar->sizeHint().boundedTo(toolBar->maximumSize())
2541 .expandedTo(toolBar->minimumSize());
2542
2543 if (toolBar->size() != hint) {
2544 QRect newGeo(oldGeo.topLeft(), hint);
2545 if (toolBar->layoutDirection() == Qt::RightToLeft)
2546 newGeo.moveRight(oldGeo.right());
2547 toolBar->setGeometry(newGeo);
2548 }
2549
2550#else
2551 Q_UNUSED(item);
2552 Q_UNUSED(dockPos);
2553#endif
2554}
2555
2556void QMainWindowLayout::revert(QLayoutItem *widgetItem)
2557{
2558 if (!savedState.isValid())
2559 return;
2560
2561 QWidget *widget = widgetItem->widget();
2562 layoutState = savedState;
2563 currentGapPos = layoutState.indexOf(widget);
2564 if (currentGapPos.isEmpty())
2565 return;
2566 fixToolBarOrientation(widgetItem, currentGapPos.at(1));
2567 layoutState.unplug(currentGapPos);
2568 layoutState.fitLayout();
2569 currentGapRect = layoutState.itemRect(currentGapPos);
2570
2571 plug(widgetItem);
2572}
2573
2574bool QMainWindowLayout::plug(QLayoutItem *widgetItem)
2575{
2576#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget) && QT_CONFIG(tabbar)
2577 if (currentHoveredFloat) {
2578 QWidget *widget = widgetItem->widget();
2579 QList<int> previousPath = layoutState.indexOf(widget);
2580 if (!previousPath.isEmpty())
2581 layoutState.remove(previousPath);
2582 previousPath = currentHoveredFloat->layoutInfo()->indexOf(widget);
2583 // Let's remove the widget from any possible group window
2584 const auto groups =
2585 parent()->findChildren<QDockWidgetGroupWindow*>(Qt::FindDirectChildrenOnly);
2586 for (QDockWidgetGroupWindow *dwgw : groups) {
2587 if (dwgw == currentHoveredFloat)
2588 continue;
2589 QList<int> path = dwgw->layoutInfo()->indexOf(widget);
2590 if (!path.isEmpty())
2591 dwgw->layoutInfo()->remove(path);
2592 }
2593 currentGapRect = QRect();
2594 currentHoveredFloat->apply();
2595 if (!previousPath.isEmpty())
2596 currentHoveredFloat->layoutInfo()->remove(previousPath);
2597 QRect globalRect = currentHoveredFloat->currentGapRect;
2598 globalRect.moveTopLeft(currentHoveredFloat->mapToGlobal(globalRect.topLeft()));
2599 pluggingWidget = widget;
2600 widgetAnimator.animate(widget, globalRect, dockOptions & QMainWindow::AnimatedDocks);
2601 return true;
2602 }
2603#endif
2604
2605 if (!parentWidget()->isVisible() || parentWidget()->isMinimized() || currentGapPos.isEmpty())
2606 return false;
2607
2608 fixToolBarOrientation(widgetItem, currentGapPos.at(1));
2609
2610 QWidget *widget = widgetItem->widget();
2611
2612#if QT_CONFIG(dockwidget)
2613 // Let's remove the widget from any possible group window
2614 const auto groups =
2615 parent()->findChildren<QDockWidgetGroupWindow*>(Qt::FindDirectChildrenOnly);
2616 for (QDockWidgetGroupWindow *dwgw : groups) {
2617 QList<int> path = dwgw->layoutInfo()->indexOf(widget);
2618 if (!path.isEmpty())
2619 dwgw->layoutInfo()->remove(path);
2620 }
2621#endif
2622
2623 QList<int> previousPath = layoutState.indexOf(widget);
2624
2625 const QLayoutItem *it = layoutState.plug(currentGapPos);
2626 if (!it)
2627 return false;
2628 Q_ASSERT(it == widgetItem);
2629 if (!previousPath.isEmpty())
2630 layoutState.remove(previousPath);
2631
2632 pluggingWidget = widget;
2633 QRect globalRect = currentGapRect;
2634 globalRect.moveTopLeft(parentWidget()->mapToGlobal(globalRect.topLeft()));
2635#if QT_CONFIG(dockwidget)
2636 if (qobject_cast<QDockWidget*>(widget) != nullptr) {
2637 QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(widget->layout());
2638 if (layout->nativeWindowDeco()) {
2639 globalRect.adjust(0, layout->titleHeight(), 0, 0);
2640 } else {
2641 int fw = widget->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, nullptr, widget);
2642 globalRect.adjust(-fw, -fw, fw, fw);
2643 }
2644 }
2645#endif
2646 widgetAnimator.animate(widget, globalRect, dockOptions & QMainWindow::AnimatedDocks);
2647
2648 return true;
2649}
2650
2651void QMainWindowLayout::animationFinished(QWidget *widget)
2652{
2653 //this function is called from within the Widget Animator whenever an animation is finished
2654 //on a certain widget
2655#if QT_CONFIG(toolbar)
2656 if (QToolBar *tb = qobject_cast<QToolBar*>(widget)) {
2657 QToolBarLayout *tbl = qobject_cast<QToolBarLayout*>(tb->layout());
2658 if (tbl->animating) {
2659 tbl->animating = false;
2660 if (tbl->expanded)
2661 tbl->layoutActions(tb->size());
2662 tb->update();
2663 }
2664 }
2665#endif
2666
2667 if (widget == pluggingWidget) {
2668
2669#if QT_CONFIG(dockwidget)
2670#if QT_CONFIG(tabbar)
2671 if (QDockWidgetGroupWindow *dwgw = qobject_cast<QDockWidgetGroupWindow *>(widget)) {
2672 // When the animated widget was a QDockWidgetGroupWindow, it means each of the
2673 // embedded QDockWidget needs to be plugged back into the QMainWindow layout.
2674 savedState.clear();
2675 QDockAreaLayoutInfo *srcInfo = dwgw->layoutInfo();
2676 const QDockAreaLayoutInfo *srcTabInfo = dwgw->tabLayoutInfo();
2677 QDockAreaLayoutInfo *dstParentInfo;
2678 QList<int> dstPath;
2679
2680 if (currentHoveredFloat) {
2681 dstPath = currentHoveredFloat->layoutInfo()->indexOf(widget);
2682 Q_ASSERT(dstPath.size() >= 1);
2683 dstParentInfo = currentHoveredFloat->layoutInfo()->info(dstPath);
2684 } else {
2685 dstPath = layoutState.dockAreaLayout.indexOf(widget);
2686 Q_ASSERT(dstPath.size() >= 2);
2687 dstParentInfo = layoutState.dockAreaLayout.info(dstPath);
2688 }
2689 Q_ASSERT(dstParentInfo);
2690 int idx = dstPath.constLast();
2691 Q_ASSERT(dstParentInfo->item_list[idx].widgetItem->widget() == dwgw);
2692 if (dstParentInfo->tabbed && srcTabInfo) {
2693 // merge the two tab widgets
2694 delete dstParentInfo->item_list[idx].widgetItem;
2695 dstParentInfo->item_list.removeAt(idx);
2696 std::copy(srcTabInfo->item_list.cbegin(), srcTabInfo->item_list.cend(),
2697 std::inserter(dstParentInfo->item_list,
2698 dstParentInfo->item_list.begin() + idx));
2699 quintptr currentId = srcTabInfo->currentTabId();
2700 *srcInfo = QDockAreaLayoutInfo();
2701 dstParentInfo->reparentWidgets(currentHoveredFloat ? currentHoveredFloat.data()
2702 : parentWidget());
2703 dstParentInfo->updateTabBar();
2704 dstParentInfo->setCurrentTabId(currentId);
2705 } else {
2706 QDockAreaLayoutItem &item = dstParentInfo->item_list[idx];
2707 Q_ASSERT(item.widgetItem->widget() == dwgw);
2708 delete item.widgetItem;
2709 item.widgetItem = nullptr;
2710 item.subinfo = new QDockAreaLayoutInfo(std::move(*srcInfo));
2711 *srcInfo = QDockAreaLayoutInfo();
2712 item.subinfo->reparentWidgets(currentHoveredFloat ? currentHoveredFloat.data()
2713 : parentWidget());
2714 item.subinfo->setTabBarShape(dstParentInfo->tabBarShape);
2715 }
2716 dwgw->destroyOrHideIfEmpty();
2717 }
2718#endif
2719
2720 if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) {
2721 dw->setParent(currentHoveredFloat ? currentHoveredFloat.data() : parentWidget());
2722 dw->show();
2723 dw->d_func()->plug(currentGapRect);
2724 }
2725#endif
2726#if QT_CONFIG(toolbar)
2727 if (QToolBar *tb = qobject_cast<QToolBar*>(widget))
2728 tb->d_func()->plug(currentGapRect);
2729#endif
2730
2731 savedState.clear();
2732 currentGapPos.clear();
2733 pluggingWidget = nullptr;
2734#if QT_CONFIG(dockwidget)
2735 setCurrentHoveredFloat(nullptr);
2736#endif
2737 //applying the state will make sure that the currentGap is updated correctly
2738 //and all the geometries (especially the one from the central widget) is correct
2739 layoutState.apply(false);
2740
2741#if QT_CONFIG(dockwidget)
2742#if QT_CONFIG(tabbar)
2743 if (qobject_cast<QDockWidget*>(widget) != nullptr) {
2744 // info() might return null if the widget is destroyed while
2745 // animating but before the animationFinished signal is received.
2746 if (QDockAreaLayoutInfo *info = dockInfo(widget))
2747 info->setCurrentTab(widget);
2748 }
2749#endif
2750#endif
2751 }
2752
2753 if (!widgetAnimator.animating()) {
2754 //all animations are finished
2755#if QT_CONFIG(dockwidget)
2756 parentWidget()->update(layoutState.dockAreaLayout.separatorRegion());
2757#if QT_CONFIG(tabbar)
2758 showTabBars();
2759#endif // QT_CONFIG(tabbar)
2760#endif // QT_CONFIG(dockwidget)
2761 }
2762
2763 updateGapIndicator();
2764}
2765
2766void QMainWindowLayout::restore(QInternal::SaveStateRule rule)
2767{
2768 if (!savedState.isValid())
2769 return;
2770
2771 layoutState = savedState;
2772 applyState(layoutState);
2773 if (rule == QInternal::ClearSavedState)
2774 savedState.clear();
2775 currentGapPos.clear();
2776 pluggingWidget = nullptr;
2777 updateGapIndicator();
2778}
2779
2780QMainWindowLayout::QMainWindowLayout(QMainWindow *mainwindow, QLayout *parentLayout)
2781 : QLayout(parentLayout ? static_cast<QWidget *>(nullptr) : mainwindow)
2782 , layoutState(mainwindow)
2783 , savedState(mainwindow)
2784 , dockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowTabbedDocks)
2785 , statusbar(nullptr)
2786#if QT_CONFIG(dockwidget)
2787#if QT_CONFIG(tabbar)
2788 , _documentMode(false)
2789 , verticalTabsEnabled(false)
2790#if QT_CONFIG(tabwidget)
2791 , _tabShape(QTabWidget::Rounded)
2792#endif
2793#endif
2794#endif // QT_CONFIG(dockwidget)
2795 , widgetAnimator(this)
2796 , pluggingWidget(nullptr)
2797{
2798 if (parentLayout)
2799 setParent(parentLayout);
2800
2801#if QT_CONFIG(dockwidget)
2802#if QT_CONFIG(tabbar)
2803 sep = mainwindow->style()->pixelMetric(QStyle::PM_DockWidgetSeparatorExtent, nullptr, mainwindow);
2804#endif
2805
2806#if QT_CONFIG(tabwidget)
2807 for (int i = 0; i < QInternal::DockCount; ++i)
2808 tabPositions[i] = QTabWidget::South;
2809#endif
2810#endif // QT_CONFIG(dockwidget)
2811 pluggingWidget = nullptr;
2812
2813 setObjectName(mainwindow->objectName() + "_layout"_L1);
2814}
2815
2816QMainWindowLayout::~QMainWindowLayout()
2817{
2818 layoutState.deleteAllLayoutItems();
2819 layoutState.deleteCentralWidgetItem();
2820
2821 delete statusbar;
2822}
2823
2824void QMainWindowLayout::setDockOptions(QMainWindow::DockOptions opts)
2825{
2826 if (opts == dockOptions)
2827 return;
2828
2829 dockOptions = opts;
2830
2831#if QT_CONFIG(dockwidget) && QT_CONFIG(tabbar)
2832 setVerticalTabsEnabled(opts & QMainWindow::VerticalTabs);
2833#endif
2834
2835 invalidate();
2836}
2837
2838#if QT_CONFIG(statusbar)
2839QStatusBar *QMainWindowLayout::statusBar() const
2840{ return statusbar ? qobject_cast<QStatusBar *>(statusbar->widget()) : 0; }
2841
2842void QMainWindowLayout::setStatusBar(QStatusBar *sb)
2843{
2844 if (sb)
2845 addChildWidget(sb);
2846 delete statusbar;
2847 statusbar = sb ? new QWidgetItemV2(sb) : nullptr;
2848 invalidate();
2849}
2850#endif // QT_CONFIG(statusbar)
2851
2852QWidget *QMainWindowLayout::centralWidget() const
2853{
2854 return layoutState.centralWidget();
2855}
2856
2857void QMainWindowLayout::setCentralWidget(QWidget *widget)
2858{
2859 if (widget != nullptr)
2860 addChildWidget(widget);
2861 layoutState.setCentralWidget(widget);
2862 if (savedState.isValid()) {
2863#if QT_CONFIG(dockwidget)
2864 savedState.dockAreaLayout.centralWidgetItem = layoutState.dockAreaLayout.centralWidgetItem;
2865 savedState.dockAreaLayout.fallbackToSizeHints = true;
2866#else
2867 savedState.centralWidgetItem = layoutState.centralWidgetItem;
2868#endif
2869 }
2870 invalidate();
2871}
2872
2873#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2874/*! \internal
2875 This helper function is called by QMainWindowLayout::unplug if QMainWindow::GroupedDragging is
2876 set and we are dragging the title bar of a non-floating QDockWidget.
2877 If one should unplug the whole group, do so and return true, otherwise return false.
2878 \a item is pointing to the QLayoutItem that holds the QDockWidget, but will be updated to the
2879 QLayoutItem that holds the new QDockWidgetGroupWindow if the group is unplugged.
2880*/
2881static bool unplugGroup(QMainWindowLayout *layout, QLayoutItem **item,
2882 QDockAreaLayoutItem &parentItem)
2883{
2884 if (!parentItem.subinfo || !parentItem.subinfo->tabbed)
2885 return false;
2886
2887 // The QDockWidget is part of a group of tab and we need to unplug them all.
2888 QDockWidgetGroupWindow *floatingTabs = layout->createTabbedDockWindow();
2889 QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
2890 *info = std::move(*parentItem.subinfo);
2891 delete parentItem.subinfo;
2892 parentItem.subinfo = nullptr;
2893 floatingTabs->setGeometry(info->rect.translated(layout->parentWidget()->pos()));
2894 floatingTabs->show();
2895 floatingTabs->raise();
2896 *item = new QDockWidgetGroupWindowItem(floatingTabs);
2897 parentItem.widgetItem = *item;
2898 return true;
2899}
2900#endif
2901
2902/*! \internal
2903 Unplug \a widget (QDockWidget or QToolBar) from it's parent container.
2904
2905 If \a group is true we might actually unplug the group of tabs this
2906 widget is part if QMainWindow::GroupedDragging is set. When \a group
2907 is false, the widget itself is always unplugged alone
2908
2909 Returns the QLayoutItem of the dragged element.
2910 The layout item is kept in the layout but set as a gap item.
2911 */
2912QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, QDockWidgetPrivate::DragScope scope)
2913{
2914#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2915 auto *groupWindow = qobject_cast<const QDockWidgetGroupWindow *>(widget->parentWidget());
2916 if (!widget->isWindow() && groupWindow) {
2917 if (scope == QDockWidgetPrivate::DragScope::Group && groupWindow->tabLayoutInfo()) {
2918 // We are just dragging a floating window as it, not need to do anything, we just have to
2919 // look up the corresponding QWidgetItem* if it exists
2920 if (QDockAreaLayoutInfo *info = dockInfo(widget->parentWidget())) {
2921 QList<int> groupWindowPath = info->indexOf(widget->parentWidget());
2922 return groupWindowPath.isEmpty() ? nullptr : info->item(groupWindowPath).widgetItem;
2923 }
2924 qCDebug(lcQpaDockWidgets) << "Drag only:" << widget << "Group:" << (scope == QDockWidgetPrivate::DragScope::Group);
2925 return nullptr;
2926 }
2927 QList<int> path = groupWindow->layoutInfo()->indexOf(widget);
2928 QDockAreaLayoutItem parentItem = groupWindow->layoutInfo()->item(path);
2929 QLayoutItem *item = parentItem.widgetItem;
2930 if (scope == QDockWidgetPrivate::DragScope::Group && path.size() > 1
2931 && unplugGroup(this, &item, parentItem)) {
2932 qCDebug(lcQpaDockWidgets) << "Unplugging:" << widget << "from" << item;
2933 return item;
2934 } else {
2935 // We are unplugging a single dock widget from a floating window.
2936 QDockWidget *dockWidget = qobject_cast<QDockWidget *>(widget);
2937 Q_ASSERT(dockWidget); // cannot be a QDockWidgetGroupWindow because it's not floating.
2938 dockWidget->d_func()->unplug(widget->geometry());
2939
2940 qCDebug(lcQpaDockWidgets) << "Unplugged from floating dock:" << widget << "from" << parentItem.widgetItem;
2941 return item;
2942 }
2943 }
2944#endif
2945 QList<int> path = layoutState.indexOf(widget);
2946 if (path.isEmpty())
2947 return nullptr;
2948
2949 QLayoutItem *item = layoutState.item(path);
2950 if (widget->isWindow())
2951 return item;
2952
2953 QRect r = layoutState.itemRect(path);
2954 savedState = layoutState;
2955
2956#if QT_CONFIG(dockwidget)
2957 if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) {
2958 Q_ASSERT(path.constFirst() == 1);
2959#if QT_CONFIG(tabwidget)
2960 if (scope == QDockWidgetPrivate::DragScope::Group && (dockOptions & QMainWindow::GroupedDragging) && path.size() > 3
2961 && unplugGroup(this, &item,
2962 layoutState.dockAreaLayout.item(path.mid(1, path.size() - 2)))) {
2963 path.removeLast();
2964 savedState = layoutState;
2965 } else
2966#endif // QT_CONFIG(tabwidget)
2967 {
2968 // Dock widget is unplugged from a main window dock
2969 // => height or width need to be decreased by separator size
2970 switch (dockWidgetArea(dw)) {
2971 case Qt::LeftDockWidgetArea:
2972 case Qt::RightDockWidgetArea:
2973 r.setHeight(r.height() - sep);
2974 break;
2975 case Qt::TopDockWidgetArea:
2976 case Qt::BottomDockWidgetArea:
2977 r.setWidth(r.width() - sep);
2978 break;
2979 case Qt::NoDockWidgetArea:
2980 case Qt::DockWidgetArea_Mask:
2981 break;
2982 }
2983
2984 // Depending on the title bar layout (vertical / horizontal),
2985 // width and height have to provide minimum space for window handles
2986 // and mouse dragging.
2987 // Assuming horizontal title bar, if the dock widget does not have a layout.
2988 const auto *layout = qobject_cast<QDockWidgetLayout *>(dw->layout());
2989 const bool verticalTitleBar = layout ? layout->verticalTitleBar : false;
2990 const int tbHeight = QApplication::style()
2991 ? QApplication::style()->pixelMetric(QStyle::PixelMetric::PM_TitleBarHeight, nullptr, dw)
2992 : 20;
2993 const int minHeight = verticalTitleBar ? 2 * tbHeight : tbHeight;
2994 const int minWidth = verticalTitleBar ? tbHeight : 2 * tbHeight;
2995 r.setSize(r.size().expandedTo(QSize(minWidth, minHeight)));
2996 qCDebug(lcQpaDockWidgets) << dw << "will be unplugged with size" << r.size();
2997
2998 dw->d_func()->unplug(r);
2999 }
3000 }
3001#endif // QT_CONFIG(dockwidget)
3002#if QT_CONFIG(toolbar)
3003 if (QToolBar *tb = qobject_cast<QToolBar*>(widget)) {
3004 tb->d_func()->unplug(r);
3005 }
3006#endif
3007
3008#if !QT_CONFIG(dockwidget) || !QT_CONFIG(tabbar)
3009 Q_UNUSED(scope);
3010#endif
3011
3012 layoutState.unplug(path ,&savedState);
3013 savedState.fitLayout();
3014 currentGapPos = path;
3015 currentGapRect = r;
3016 updateGapIndicator();
3017
3018 fixToolBarOrientation(item, currentGapPos.at(1));
3019
3020 return item;
3021}
3022
3023void QMainWindowLayout::updateGapIndicator()
3024{
3025#if QT_CONFIG(rubberband)
3026 if (!widgetAnimator.animating() && (!currentGapPos.isEmpty()
3027#if QT_CONFIG(dockwidget)
3028 || currentHoveredFloat
3029#endif
3030 )) {
3031 QWidget *expectedParent =
3032#if QT_CONFIG(dockwidget)
3033 currentHoveredFloat ? currentHoveredFloat.data() :
3034#endif
3035 parentWidget();
3036 if (!gapIndicator) {
3037 gapIndicator = new QRubberBand(QRubberBand::Rectangle, expectedParent);
3038 // For accessibility to identify this special widget.
3039 gapIndicator->setObjectName("qt_rubberband"_L1);
3040 } else if (gapIndicator->parent() != expectedParent) {
3041 gapIndicator->setParent(expectedParent);
3042 }
3043
3044 // Prevent re-entry in case of size change
3045 const bool sigBlockState = gapIndicator->signalsBlocked();
3046 auto resetSignals = qScopeGuard([this, sigBlockState](){ gapIndicator->blockSignals(sigBlockState); });
3047 gapIndicator->blockSignals(true);
3048
3049#if QT_CONFIG(dockwidget)
3050 if (currentHoveredFloat)
3051 gapIndicator->setGeometry(currentHoveredFloat->currentGapRect);
3052 else
3053#endif
3054 gapIndicator->setGeometry(currentGapRect);
3055
3056 gapIndicator->show();
3057 gapIndicator->raise();
3058
3059 // Reset signal state
3060
3061 } else if (gapIndicator) {
3062 gapIndicator->hide();
3063 }
3064
3065#endif // QT_CONFIG(rubberband)
3066}
3067
3068void QMainWindowLayout::hover(QLayoutItem *hoverTarget,
3069 const QPoint &mousePos) {
3070 if (!parentWidget()->isVisible() || parentWidget()->isMinimized() ||
3071 pluggingWidget != nullptr || hoverTarget == nullptr)
3072 return;
3073
3074 QWidget *widget = hoverTarget->widget();
3075
3076#if QT_CONFIG(dockwidget)
3077 widget->raise();
3078 if ((dockOptions & QMainWindow::GroupedDragging) && (qobject_cast<QDockWidget*>(widget)
3079 || qobject_cast<QDockWidgetGroupWindow *>(widget))) {
3080
3081 // Check if we are over another floating dock widget
3082 QVarLengthArray<QWidget *, 10> candidates;
3083 const auto siblings = parentWidget()->children();
3084 for (QObject *c : siblings) {
3085 QWidget *w = qobject_cast<QWidget*>(c);
3086 if (!w)
3087 continue;
3088
3089 // Handle only dock widgets and group windows
3090 if (!qobject_cast<QDockWidget*>(w) && !qobject_cast<QDockWidgetGroupWindow *>(w))
3091 continue;
3092
3093 // Check permission to dock on another dock widget or floating dock
3094 // FIXME in Qt 7
3095
3096 if (w != widget && w->isWindow() && w->isVisible() && !w->isMinimized())
3097 candidates << w;
3098
3099 if (QDockWidgetGroupWindow *group = qobject_cast<QDockWidgetGroupWindow *>(w)) {
3100 // floating QDockWidgets have a QDockWidgetGroupWindow as a parent,
3101 // if they have been hovered over
3102 const auto groupChildren = group->children();
3103 for (QObject *c : groupChildren) {
3104 if (QDockWidget *dw = qobject_cast<QDockWidget*>(c)) {
3105 if (dw != widget && dw->isFloating() && dw->isVisible() && !dw->isMinimized())
3106 candidates << dw;
3107 }
3108 }
3109 }
3110 }
3111
3112 for (QWidget *w : candidates) {
3113 const QScreen *screen1 = qt_widget_private(widget)->associatedScreen();
3114 const QScreen *screen2 = qt_widget_private(w)->associatedScreen();
3115 if (screen1 && screen2 && screen1 != screen2)
3116 continue;
3117 if (!w->geometry().contains(mousePos))
3118 continue;
3119
3120#if QT_CONFIG(tabwidget)
3121 if (auto dropTo = qobject_cast<QDockWidget *>(w)) {
3122
3123 // w is the drop target's widget
3124 w = dropTo->widget();
3125
3126 // Create a floating tab, unless already existing
3127 if (!qobject_cast<QDockWidgetGroupWindow *>(w)) {
3128 QDockWidgetGroupWindow *floatingTabs = createTabbedDockWindow();
3129 floatingTabs->setGeometry(dropTo->geometry());
3130 QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
3131 const QTabBar::Shape shape = tabwidgetPositionToTabBarShape(dropTo);
3132
3133 // dropTo and widget may be in a state where they transition
3134 // from being a group window child to a single floating dock widget.
3135 // In that case, their path to a main window dock may not have been
3136 // updated yet.
3137 // => ask both and fall back to dock 1 (right dock)
3138 QInternal::DockPosition dockPosition = toDockPos(dockWidgetArea(dropTo));
3139 if (dockPosition == QInternal::DockPosition::DockCount)
3140 dockPosition = toDockPos(dockWidgetArea(widget));
3141 if (dockPosition == QInternal::DockPosition::DockCount)
3142 dockPosition = QInternal::DockPosition::RightDock;
3143
3144 *info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, dockPosition,
3145 Qt::Horizontal, shape,
3146 static_cast<QMainWindow *>(parentWidget()));
3147 info->tabBar = getTabBar();
3148 info->tabbed = true;
3149 info->add(dropTo);
3150 QDockAreaLayoutInfo &parentInfo = layoutState.dockAreaLayout.docks[dockPosition];
3151 parentInfo.add(floatingTabs);
3152 dropTo->setParent(floatingTabs);
3153 qCDebug(lcQpaDockWidgets) << "Wrapping" << widget << "into floating tabs" << floatingTabs;
3154 w = floatingTabs;
3155 }
3156
3157 // Show the drop target and raise widget to foreground
3158 dropTo->show();
3159 qCDebug(lcQpaDockWidgets) << "Showing" << dropTo;
3160 widget->raise();
3161 qCDebug(lcQpaDockWidgets) << "Raising" << widget;
3162 }
3163#endif
3164 auto *groupWindow = qobject_cast<QDockWidgetGroupWindow *>(w);
3165 Q_ASSERT(groupWindow);
3166 if (groupWindow->hover(hoverTarget, groupWindow->mapFromGlobal(mousePos))) {
3167 setCurrentHoveredFloat(groupWindow);
3168 applyState(layoutState); // update the tabbars
3169 }
3170 return;
3171 }
3172 }
3173
3174 // If a temporary group window has been created during a hover,
3175 // remove it, if it has only one dockwidget child
3176 if (currentHoveredFloat)
3177 currentHoveredFloat->destroyIfSingleItemLeft();
3178
3179 setCurrentHoveredFloat(nullptr);
3180 layoutState.dockAreaLayout.fallbackToSizeHints = false;
3181#endif // QT_CONFIG(dockwidget)
3182
3183 QPoint pos = parentWidget()->mapFromGlobal(mousePos);
3184
3185 if (!savedState.isValid())
3186 savedState = layoutState;
3187
3188 QList<int> path = savedState.gapIndex(widget, pos);
3189
3190 if (!path.isEmpty()) {
3191 bool allowed = false;
3192
3193#if QT_CONFIG(dockwidget)
3194 allowed = isAreaAllowed(widget, path);
3195#endif
3196#if QT_CONFIG(toolbar)
3197 if (QToolBar *tb = qobject_cast<QToolBar*>(widget))
3198 allowed = tb->isAreaAllowed(toToolBarArea(path.at(1)));
3199#endif
3200
3201 if (!allowed)
3202 path.clear();
3203 }
3204
3205 if (path == currentGapPos)
3206 return; // the gap is already there
3207
3208 currentGapPos = path;
3209 if (path.isEmpty()) {
3210 fixToolBarOrientation(hoverTarget, 2); // 2 = top dock, ie. horizontal
3211 restore(QInternal::KeepSavedState);
3212 return;
3213 }
3214
3215 fixToolBarOrientation(hoverTarget, currentGapPos.at(1));
3216
3217 QMainWindowLayoutState newState = savedState;
3218
3219 if (!newState.insertGap(path, hoverTarget)) {
3220 restore(QInternal::KeepSavedState); // not enough space
3221 return;
3222 }
3223
3224 QSize min = newState.minimumSize();
3225 QSize size = newState.rect.size();
3226
3227 if (min.width() > size.width() || min.height() > size.height()) {
3228 restore(QInternal::KeepSavedState);
3229 return;
3230 }
3231
3232 newState.fitLayout();
3233
3234 currentGapRect = newState.gapRect(currentGapPos);
3235
3236#if QT_CONFIG(dockwidget)
3237 parentWidget()->update(layoutState.dockAreaLayout.separatorRegion());
3238#endif
3239 layoutState = std::move(newState);
3240 applyState(layoutState);
3241
3242 updateGapIndicator();
3243}
3244
3245#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
3246QDockWidgetGroupWindow *QMainWindowLayout::createTabbedDockWindow()
3247{
3248 QDockWidgetGroupWindow* f = new QDockWidgetGroupWindow(parentWidget(), Qt::Tool);
3249 new QDockWidgetGroupLayout(f);
3250 return f;
3251}
3252#endif
3253
3254void QMainWindowLayout::applyState(QMainWindowLayoutState &newState, bool animate)
3255{
3256 // applying the state can lead to showing separator widgets, which would lead to a re-layout
3257 // (even though the separator widgets are not really part of the layout)
3258 // break the loop
3259 if (isInApplyState)
3260 return;
3261 isInApplyState = true;
3262#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
3263 QSet<QTabBar*> used = newState.dockAreaLayout.usedTabBars();
3264 const auto groups =
3265 parent()->findChildren<QDockWidgetGroupWindow*>(Qt::FindDirectChildrenOnly);
3266 for (QDockWidgetGroupWindow *dwgw : groups)
3267 used += dwgw->layoutInfo()->usedTabBars();
3268
3269 const QSet<QTabBar*> retired = usedTabBars - used;
3270 usedTabBars = used;
3271 for (QTabBar *tab_bar : retired) {
3272 unuseTabBar(tab_bar);
3273 }
3274
3275 if (sep == 1) {
3276 const QSet<QWidget*> usedSeps = newState.dockAreaLayout.usedSeparatorWidgets();
3277 const QSet<QWidget*> retiredSeps = usedSeparatorWidgets - usedSeps;
3278 usedSeparatorWidgets = usedSeps;
3279 for (QWidget *sepWidget : retiredSeps)
3280 delete sepWidget;
3281 }
3282
3283 for (int i = 0; i < QInternal::DockCount; ++i)
3284 newState.dockAreaLayout.docks[i].reparentWidgets(parentWidget());
3285
3286#endif // QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
3287 newState.apply(dockOptions & QMainWindow::AnimatedDocks && animate);
3288 isInApplyState = false;
3289}
3290
3291void QMainWindowLayout::saveState(QDataStream &stream) const
3292{
3293 layoutState.saveState(stream);
3294}
3295
3296bool QMainWindowLayout::restoreState(QDataStream &stream)
3297{
3298 QScopedValueRollback<bool> guard(isInRestoreState, true);
3299 savedState = layoutState;
3300 layoutState.clear();
3301 layoutState.rect = savedState.rect;
3302
3303 if (!layoutState.restoreState(stream, savedState)) {
3304 layoutState.deleteAllLayoutItems();
3305 layoutState = savedState;
3306 if (parentWidget()->isVisible())
3307 applyState(layoutState, false); // hides tabBars allocated by newState
3308 return false;
3309 }
3310
3311 if (parentWidget()->isVisible()) {
3312 layoutState.fitLayout();
3313 applyState(layoutState, false);
3314 } else {
3315 /*
3316 The state might not fit into the size of the widget as it gets shown, but
3317 if the window is expected to be maximized or full screened, then we might
3318 get several resizes as part of that transition, at the end of which the
3319 state might fit. So keep the restored state around for now and try again
3320 later in setGeometry.
3321 */
3322 if ((parentWidget()->windowState() & (Qt::WindowFullScreen | Qt::WindowMaximized))
3323 && !layoutState.fits()) {
3324 restoredState.reset(new QMainWindowLayoutState(layoutState));
3325 }
3326 }
3327
3328 savedState.deleteAllLayoutItems();
3329 savedState.clear();
3330
3331#if QT_CONFIG(dockwidget) && QT_CONFIG(tabbar)
3332 if (parentWidget()->isVisible())
3333 showTabBars();
3334#endif // QT_CONFIG(dockwidget)/QT_CONFIG(tabbar)
3335
3336 return true;
3337}
3338
3339#if QT_CONFIG(draganddrop)
3340bool QMainWindowLayout::needsPlatformDrag()
3341{
3342 static const bool wayland =
3343 QGuiApplication::platformName().startsWith("wayland"_L1, Qt::CaseInsensitive);
3344 return wayland;
3345}
3346
3347Qt::DropAction QMainWindowLayout::performPlatformWidgetDrag(QLayoutItem *widgetItem,
3348 const QPoint &pressPosition)
3349{
3350 draggingWidget = widgetItem;
3351 QWidget *widget = widgetItem->widget();
3352 auto drag = QDrag(widget);
3353 auto mimeData = new QMimeData();
3354 auto window = widgetItem->widget()->windowHandle();
3355
3356 auto serialize = [](const auto &object) {
3357 QByteArray data;
3358 QDataStream dataStream(&data, QIODevice::WriteOnly);
3359 dataStream << object;
3360 return data;
3361 };
3362 mimeData->setData("application/x-qt-mainwindowdrag-window"_L1,
3363 serialize(reinterpret_cast<qintptr>(window)));
3364 mimeData->setData("application/x-qt-mainwindowdrag-position"_L1, serialize(pressPosition));
3365 drag.setMimeData(mimeData);
3366
3367 auto result = drag.exec();
3368
3369 draggingWidget = nullptr;
3370 return result;
3371}
3372#endif
3373
3374QT_END_NAMESPACE
3375
3376#include "qmainwindowlayout.moc"
3377#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)