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