1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Copyright (C) 2015 Olivier Goffart <ogoffart@woboq.com>
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the QtWidgets module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU Lesser General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU Lesser
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
22 ** packaging of this file. Please review the following information to
23 ** ensure the GNU Lesser General Public License version 3 requirements
24 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25 **
26 ** GNU General Public License Usage
27 ** Alternatively, this file may be used under the terms of the GNU
28 ** General Public License version 2.0 or (at your option) the GNU General
29 ** Public license version 3 or any later version approved by the KDE Free
30 ** Qt Foundation. The licenses are as published by the Free Software
31 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32 ** included in the packaging of this file. Please review the following
33 ** information to ensure the GNU General Public License requirements will
34 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35 ** https://www.gnu.org/licenses/gpl-3.0.html.
36 **
37 ** $QT_END_LICENSE$
38 **
39 ****************************************************************************/
40 
41 #include "qmainwindowlayout_p.h"
42 
43 #if QT_CONFIG(dockwidget)
44 #include "qdockarealayout_p.h"
45 #include "qdockwidget.h"
46 #include "qdockwidget_p.h"
47 #endif
48 #if QT_CONFIG(toolbar)
49 #include "qtoolbar_p.h"
50 #include "qtoolbar.h"
51 #include "qtoolbarlayout_p.h"
52 #endif
53 #include "qmainwindow.h"
54 #include "qwidgetanimator_p.h"
55 #if QT_CONFIG(rubberband)
56 #include "qrubberband.h"
57 #endif
58 #if QT_CONFIG(tabbar)
59 #include "qtabbar_p.h"
60 #endif
61 
62 #include <qapplication.h>
63 #if QT_CONFIG(statusbar)
64 #include <qstatusbar.h>
65 #endif
66 #include <qstring.h>
67 #include <qstyle.h>
68 #include <qstylepainter.h>
69 #include <qvarlengtharray.h>
70 #include <qstack.h>
71 #include <qmap.h>
72 #include <qtimer.h>
73 #include <qpointer.h>
74 
75 #ifndef QT_NO_DEBUG_STREAM
76 #  include <qdebug.h>
77 #  include <qtextstream.h>
78 #endif
79 
80 #include <private/qmenu_p.h>
81 #include <private/qapplication_p.h>
82 #include <private/qlayoutengine_p.h>
83 #include <private/qwidgetresizehandler_p.h>
84 
85 QT_BEGIN_NAMESPACE
86 
87 extern QMainWindowLayout *qt_mainwindow_layout(const QMainWindow *window);
88 
89 /******************************************************************************
90 ** debug
91 */
92 
93 #if QT_CONFIG(dockwidget) && !defined(QT_NO_DEBUG_STREAM)
94 
95 static void dumpLayout(QTextStream &qout, const QDockAreaLayoutInfo &layout, QString indent);
96 
dumpLayout(QTextStream & qout,const QDockAreaLayoutItem & item,QString indent)97 static void dumpLayout(QTextStream &qout, const QDockAreaLayoutItem &item, QString indent)
98 {
99     qout << indent << "QDockAreaLayoutItem: "
100             << "pos: " << item.pos << " size:" << item.size
101             << " gap:" << (item.flags & QDockAreaLayoutItem::GapItem)
102             << " keepSize:" << (item.flags & QDockAreaLayoutItem::KeepSize) << '\n';
103     indent += QLatin1String("  ");
104     if (item.widgetItem != nullptr) {
105         qout << indent << "widget: "
106             << item.widgetItem->widget()->metaObject()->className()
107             << " \"" << item.widgetItem->widget()->windowTitle() << "\"\n";
108     } else if (item.subinfo != nullptr) {
109         qout << indent << "subinfo:\n";
110         dumpLayout(qout, *item.subinfo, indent + QLatin1String("  "));
111     } else if (item.placeHolderItem != nullptr) {
112         QRect r = item.placeHolderItem->topLevelRect;
113         qout << indent << "placeHolder: "
114             << "pos: " << item.pos << " size:" << item.size
115             << " gap:" << (item.flags & QDockAreaLayoutItem::GapItem)
116             << " keepSize:" << (item.flags & QDockAreaLayoutItem::KeepSize)
117             << " objectName:" << item.placeHolderItem->objectName
118             << " hidden:" << item.placeHolderItem->hidden
119             << " window:" << item.placeHolderItem->window
120             << " rect:" << r.x() << ',' << r.y() << ' '
121             << r.width() << 'x' << r.height() << '\n';
122     }
123 }
124 
dumpLayout(QTextStream & qout,const QDockAreaLayoutInfo & layout,QString indent)125 static void dumpLayout(QTextStream &qout, const QDockAreaLayoutInfo &layout, QString indent)
126 {
127     const QSize minSize = layout.minimumSize();
128     qout << indent << "QDockAreaLayoutInfo: "
129             << layout.rect.left() << ','
130             << layout.rect.top() << ' '
131             << layout.rect.width() << 'x'
132             << layout.rect.height()
133             << " min size: " << minSize.width() << ',' << minSize.height()
134             << " orient:" << layout.o
135 #if QT_CONFIG(tabbar)
136             << " tabbed:" << layout.tabbed
137             << " tbshape:" << layout.tabBarShape
138 #endif
139             << '\n';
140 
141     indent += QLatin1String("  ");
142 
143     for (int i = 0; i < layout.item_list.count(); ++i) {
144         qout << indent << "Item: " << i << '\n';
145         dumpLayout(qout, layout.item_list.at(i), indent + QLatin1String("  "));
146     }
147 }
148 
dumpLayout(QTextStream & qout,const QDockAreaLayout & layout)149 static void dumpLayout(QTextStream &qout, const QDockAreaLayout &layout)
150 {
151     qout << "QDockAreaLayout: "
152             << layout.rect.left() << ','
153             << layout.rect.top() << ' '
154             << layout.rect.width() << 'x'
155             << layout.rect.height() << '\n';
156 
157     qout << "TopDockArea:\n";
158     dumpLayout(qout, layout.docks[QInternal::TopDock], QLatin1String("  "));
159     qout << "LeftDockArea:\n";
160     dumpLayout(qout, layout.docks[QInternal::LeftDock], QLatin1String("  "));
161     qout << "RightDockArea:\n";
162     dumpLayout(qout, layout.docks[QInternal::RightDock], QLatin1String("  "));
163     qout << "BottomDockArea:\n";
164     dumpLayout(qout, layout.docks[QInternal::BottomDock], QLatin1String("  "));
165 }
166 
operator <<(QDebug debug,const QDockAreaLayout & layout)167 QDebug operator<<(QDebug debug, const QDockAreaLayout &layout)
168 {
169     QString s;
170     QTextStream str(&s);
171     dumpLayout(str, layout);
172     debug << s;
173     return debug;
174 }
175 
operator <<(QDebug debug,const QMainWindowLayout * layout)176 QDebug operator<<(QDebug debug, const QMainWindowLayout *layout)
177 {
178     debug << layout->layoutState.dockAreaLayout;
179     return debug;
180 }
181 
182 #endif // QT_CONFIG(dockwidget) && !defined(QT_NO_DEBUG)
183 
184 /******************************************************************************
185  ** QDockWidgetGroupWindow
186  */
187 // QDockWidgetGroupWindow is the floating window containing several QDockWidgets floating together.
188 // (QMainWindow::GroupedDragging feature)
189 // QDockWidgetGroupLayout is the layout of that window and use a QDockAreaLayoutInfo to layout
190 // the QDockWidgets inside it.
191 // If there is only one QDockWidgets, or all QDockWidgets are tabbed together, it is equivalent
192 // of a floating QDockWidget (the title of the QDockWidget is the title of the window). But if there
193 // are nested QDockWidget, an additional title bar is there.
194 #if QT_CONFIG(dockwidget)
195 class QDockWidgetGroupLayout : public QLayout,
196                                public QMainWindowLayoutSeparatorHelper<QDockWidgetGroupLayout>
197 {
198     QWidgetResizeHandler *resizer;
199 public:
QDockWidgetGroupLayout(QDockWidgetGroupWindow * parent)200     QDockWidgetGroupLayout(QDockWidgetGroupWindow* parent) : QLayout(parent) {
201         setSizeConstraint(QLayout::SetMinAndMaxSize);
202         resizer = new QWidgetResizeHandler(parent);
203         resizer->setMovingEnabled(false);
204     }
~QDockWidgetGroupLayout()205     ~QDockWidgetGroupLayout() {
206         layoutState.deleteAllLayoutItems();
207     }
208 
addItem(QLayoutItem *)209     void addItem(QLayoutItem*) override { Q_UNREACHABLE(); }
count() const210     int count() const override { return 0; }
itemAt(int index) const211     QLayoutItem* itemAt(int index) const override
212     {
213         int x = 0;
214         return layoutState.itemAt(&x, index);
215     }
takeAt(int index)216     QLayoutItem* takeAt(int index) override
217     {
218         int x = 0;
219         QLayoutItem *ret = layoutState.takeAt(&x, index);
220         if (savedState.rect.isValid() && ret->widget()) {
221             // we need to remove the item also from the saved state to prevent crash
222             QList<int> path = savedState.indexOf(ret->widget());
223             if (!path.isEmpty())
224                 savedState.remove(path);
225             // Also, the item may be contained several times as a gap item.
226             path = layoutState.indexOf(ret->widget());
227             if (!path.isEmpty())
228                 layoutState.remove(path);
229         }
230         return ret;
231     }
sizeHint() const232     QSize sizeHint() const override
233     {
234         int fw = frameWidth();
235         return layoutState.sizeHint() + QSize(fw, fw);
236     }
minimumSize() const237     QSize minimumSize() const override
238     {
239         int fw = frameWidth();
240         return layoutState.minimumSize() + QSize(fw, fw);
241     }
maximumSize() const242     QSize maximumSize() const override
243     {
244         int fw = frameWidth();
245         return layoutState.maximumSize() + QSize(fw, fw);
246     }
setGeometry(const QRect & r)247     void setGeometry(const QRect&r) override
248     {
249         groupWindow()->destroyOrHideIfEmpty();
250         QDockAreaLayoutInfo *li = dockAreaLayoutInfo();
251         if (li->isEmpty())
252             return;
253         int fw = frameWidth();
254 #if QT_CONFIG(tabbar)
255         li->reparentWidgets(parentWidget());
256 #endif
257         li->rect = r.adjusted(fw, fw, -fw, -fw);
258         li->fitItems();
259         li->apply(false);
260         if (savedState.rect.isValid())
261             savedState.rect = li->rect;
262         resizer->setActive(QWidgetResizeHandler::Resize, !nativeWindowDeco());
263     }
264 
dockAreaLayoutInfo()265     QDockAreaLayoutInfo *dockAreaLayoutInfo() { return &layoutState; }
266 
nativeWindowDeco() const267     bool nativeWindowDeco() const
268     {
269         return groupWindow()->hasNativeDecos();
270     }
271 
frameWidth() const272     int frameWidth() const
273     {
274         return nativeWindowDeco() ? 0 :
275             parentWidget()->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, nullptr, parentWidget());
276     }
277 
groupWindow() const278     QDockWidgetGroupWindow *groupWindow() const
279     {
280         return static_cast<QDockWidgetGroupWindow *>(parent());
281     }
282 
283     QDockAreaLayoutInfo layoutState;
284     QDockAreaLayoutInfo savedState;
285 };
286 
event(QEvent * e)287 bool QDockWidgetGroupWindow::event(QEvent *e)
288 {
289     auto lay = static_cast<QDockWidgetGroupLayout *>(layout());
290     if (lay && lay->windowEvent(e))
291         return true;
292 
293     switch (e->type()) {
294     case QEvent::Close:
295 #if QT_CONFIG(tabbar)
296         // Forward the close to the QDockWidget just as if its close button was pressed
297         if (QDockWidget *dw = activeTabbedDockWidget()) {
298             e->ignore();
299             dw->close();
300             adjustFlags();
301         }
302 #endif
303         return true;
304     case QEvent::Move:
305 #if QT_CONFIG(tabbar)
306         // Let QDockWidgetPrivate::moseEvent handle the dragging
307         if (QDockWidget *dw = activeTabbedDockWidget())
308             static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(dw))->moveEvent(static_cast<QMoveEvent*>(e));
309 #endif
310         return true;
311     case QEvent::NonClientAreaMouseMove:
312     case QEvent::NonClientAreaMouseButtonPress:
313     case QEvent::NonClientAreaMouseButtonRelease:
314     case QEvent::NonClientAreaMouseButtonDblClick:
315 #if QT_CONFIG(tabbar)
316         // Let the QDockWidgetPrivate of the currently visible dock widget handle the drag and drop
317         if (QDockWidget *dw = activeTabbedDockWidget())
318             static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(dw))->nonClientAreaMouseEvent(static_cast<QMouseEvent*>(e));
319 #endif
320         return true;
321     case QEvent::ChildAdded:
322         if (qobject_cast<QDockWidget *>(static_cast<QChildEvent*>(e)->child()))
323             adjustFlags();
324         break;
325     case QEvent::LayoutRequest:
326         // We might need to show the widget again
327         destroyOrHideIfEmpty();
328         break;
329     case QEvent::Resize:
330         updateCurrentGapRect();
331         emit resized();
332     default:
333         break;
334     }
335     return QWidget::event(e);
336 }
337 
paintEvent(QPaintEvent *)338 void QDockWidgetGroupWindow::paintEvent(QPaintEvent *)
339 {
340     QDockWidgetGroupLayout *lay = static_cast<QDockWidgetGroupLayout *>(layout());
341     bool nativeDeco = lay->nativeWindowDeco();
342 
343     if (!nativeDeco) {
344         QStyleOptionFrame framOpt;
345         framOpt.init(this);
346         QStylePainter p(this);
347         p.drawPrimitive(QStyle::PE_FrameDockWidget, framOpt);
348     }
349 }
350 
layoutInfo() const351 QDockAreaLayoutInfo *QDockWidgetGroupWindow::layoutInfo() const
352 {
353     return static_cast<QDockWidgetGroupLayout *>(layout())->dockAreaLayoutInfo();
354 }
355 
356 #if QT_CONFIG(tabbar)
357 /*! \internal
358     If this is a floating tab bar returns the currently the QDockWidgetGroupWindow that contains
359     tab, otherwise, return nullptr;
360     \note: if there is only one QDockWidget, it's still considered as a floating tab
361  */
tabLayoutInfo() const362 const QDockAreaLayoutInfo *QDockWidgetGroupWindow::tabLayoutInfo() const
363 {
364     const QDockAreaLayoutInfo *info = layoutInfo();
365     while (info && !info->tabbed) {
366         // There should be only one tabbed subinfo otherwise we are not a floating tab but a real
367         // window
368         const QDockAreaLayoutInfo *next = nullptr;
369         bool isSingle = false;
370         for (const auto &item : info->item_list) {
371             if (item.skip() || (item.flags & QDockAreaLayoutItem::GapItem))
372                 continue;
373             if (next || isSingle) // Two visible things
374                 return nullptr;
375             if (item.subinfo)
376                 next = item.subinfo;
377             else if (item.widgetItem)
378                 isSingle = true;
379         }
380         if (isSingle)
381             return info;
382         info = next;
383     }
384     return info;
385 }
386 
387 /*! \internal
388     If this is a floating tab bar returns the currently active QDockWidget, otherwise nullptr
389  */
activeTabbedDockWidget() const390 QDockWidget *QDockWidgetGroupWindow::activeTabbedDockWidget() const
391 {
392     QDockWidget *dw = nullptr;
393     const QDockAreaLayoutInfo *info = tabLayoutInfo();
394     if (!info)
395         return nullptr;
396     if (info->tabBar && info->tabBar->currentIndex() >= 0) {
397         int i = info->tabIndexToListIndex(info->tabBar->currentIndex());
398         if (i >= 0) {
399             const QDockAreaLayoutItem &item = info->item_list.at(i);
400             if (item.widgetItem)
401                 dw = qobject_cast<QDockWidget *>(item.widgetItem->widget());
402         }
403     }
404     if (!dw) {
405         for (int i = 0; !dw && i < info->item_list.count(); ++i) {
406             const QDockAreaLayoutItem &item = info->item_list.at(i);
407             if (item.skip())
408                 continue;
409             if (!item.widgetItem)
410                 continue;
411             dw = qobject_cast<QDockWidget *>(item.widgetItem->widget());
412         }
413     }
414     return dw;
415 }
416 #endif // QT_CONFIG(tabbar)
417 
418 /*! \internal
419     Destroy or hide this window if there is no more QDockWidget in it.
420     Otherwise make sure it is shown.
421  */
destroyOrHideIfEmpty()422 void QDockWidgetGroupWindow::destroyOrHideIfEmpty()
423 {
424     if (!layoutInfo()->isEmpty()) {
425         show(); // It might have been hidden,
426         return;
427     }
428     // There might still be placeholders
429     if (!layoutInfo()->item_list.isEmpty()) {
430         hide();
431         return;
432     }
433 
434     // Make sure to reparent the possibly floating or hidden QDockWidgets to the parent
435     const auto dockWidgets = findChildren<QDockWidget *>(QString(), Qt::FindDirectChildrenOnly);
436     for (QDockWidget *dw : dockWidgets) {
437         bool wasFloating = dw->isFloating();
438         bool wasHidden = dw->isHidden();
439         dw->setParent(parentWidget());
440         if (wasFloating) {
441             dw->setFloating(true);
442         } else {
443             // maybe it was hidden, we still have to put it back in the main layout.
444             QMainWindowLayout *ml =
445                 qt_mainwindow_layout(static_cast<QMainWindow *>(parentWidget()));
446             Qt::DockWidgetArea area = ml->dockWidgetArea(this);
447             if (area == Qt::NoDockWidgetArea)
448                 area = Qt::LeftDockWidgetArea;
449             static_cast<QMainWindow *>(parentWidget())->addDockWidget(area, dw);
450         }
451         if (!wasHidden)
452             dw->show();
453     }
454 #if QT_CONFIG(tabbar)
455     const auto tabBars = findChildren<QTabBar *>(QString(), Qt::FindDirectChildrenOnly);
456     for (QTabBar *tb : tabBars)
457         tb->setParent(parentWidget());
458 #endif
459     deleteLater();
460 }
461 
462 /*! \internal
463     Sets the flags of this window in accordance to the capabilities of the dock widgets
464  */
adjustFlags()465 void QDockWidgetGroupWindow::adjustFlags()
466 {
467     Qt::WindowFlags oldFlags = windowFlags();
468     Qt::WindowFlags flags = oldFlags;
469 
470 #if QT_CONFIG(tabbar)
471     QDockWidget *top = activeTabbedDockWidget();
472 #else
473     QDockWidget *top = nullptr;
474 #endif
475     if (!top) { // nested tabs, show window decoration
476         flags =
477             ((oldFlags & ~Qt::FramelessWindowHint) | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
478     } else if (static_cast<QDockWidgetGroupLayout *>(layout())->nativeWindowDeco()) {
479         flags |= Qt::CustomizeWindowHint | Qt::WindowTitleHint;
480         flags.setFlag(Qt::WindowCloseButtonHint, top->features() & QDockWidget::DockWidgetClosable);
481         flags &= ~Qt::FramelessWindowHint;
482     } else {
483         flags &= ~(Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint | Qt::WindowTitleHint);
484         flags |= Qt::FramelessWindowHint;
485     }
486 
487     if (oldFlags != flags) {
488         if (!windowHandle())
489             create(); // The desired geometry is forgotten if we call setWindowFlags before having a window
490         setWindowFlags(flags);
491         const bool gainedNativeDecos = (oldFlags & Qt::FramelessWindowHint) && !(flags & Qt::FramelessWindowHint);
492         const bool lostNativeDecos = !(oldFlags & Qt::FramelessWindowHint) && (flags & Qt::FramelessWindowHint);
493 
494         // Adjust the geometry after gaining/losing decos, so that the client area appears always
495         // at the same place when tabbing
496         if (lostNativeDecos) {
497             QRect newGeometry = geometry();
498             newGeometry.setTop(frameGeometry().top());
499             const int bottomFrame = geometry().top() - frameGeometry().top();
500             m_removedFrameSize = QSize((frameSize() - size()).width(), bottomFrame);
501             setGeometry(newGeometry);
502         } else if (gainedNativeDecos && m_removedFrameSize.isValid()) {
503             QRect r = geometry();
504             r.adjust(-m_removedFrameSize.width() / 2, 0,
505                      -m_removedFrameSize.width() / 2, -m_removedFrameSize.height());
506             setGeometry(r);
507             m_removedFrameSize = QSize();
508         }
509 
510         show(); // setWindowFlags hides the window
511     }
512 
513     QWidget *titleBarOf = top ? top : parentWidget();
514     setWindowTitle(titleBarOf->windowTitle());
515     setWindowIcon(titleBarOf->windowIcon());
516 }
517 
hasNativeDecos() const518 bool QDockWidgetGroupWindow::hasNativeDecos() const
519 {
520 #if QT_CONFIG(tabbar)
521     QDockWidget *dw = activeTabbedDockWidget();
522     if (!dw) // We have a group of nested QDockWidgets (not just floating tabs)
523         return true;
524 
525     if (!QDockWidgetLayout::wmSupportsNativeWindowDeco())
526         return false;
527 
528     return dw->titleBarWidget() == nullptr;
529 #else
530     return true;
531 #endif
532 }
533 
534 /*
535     The given widget is hovered over this floating group.
536     This function will save the state and create a gap in the actual state.
537     currentGapRect and currentGapPos will be set.
538     One must call restore() or apply() after this function.
539     Returns true if there was any change in the currentGapPos
540  */
hover(QLayoutItem * widgetItem,const QPoint & mousePos)541 bool QDockWidgetGroupWindow::hover(QLayoutItem *widgetItem, const QPoint &mousePos)
542 {
543     QDockAreaLayoutInfo &savedState = static_cast<QDockWidgetGroupLayout *>(layout())->savedState;
544     if (savedState.isEmpty())
545         savedState = *layoutInfo();
546 
547     QMainWindow::DockOptions opts = static_cast<QMainWindow *>(parentWidget())->dockOptions();
548     QDockAreaLayoutInfo newState = savedState;
549     bool nestingEnabled =
550         (opts & QMainWindow::AllowNestedDocks) && !(opts & QMainWindow::ForceTabbedDocks);
551     QDockAreaLayoutInfo::TabMode tabMode =
552 #if !QT_CONFIG(tabbar)
553         QDockAreaLayoutInfo::NoTabs;
554 #else
555         nestingEnabled ? QDockAreaLayoutInfo::AllowTabs : QDockAreaLayoutInfo::ForceTabs;
556     if (auto group = qobject_cast<QDockWidgetGroupWindow *>(widgetItem->widget())) {
557         if (!group->tabLayoutInfo())
558             tabMode = QDockAreaLayoutInfo::NoTabs;
559     }
560     if (newState.tabbed) {
561         // insertion into a top-level tab
562         newState.item_list = { QDockAreaLayoutItem(new QDockAreaLayoutInfo(newState)) };
563         newState.item_list.first().size = pick(savedState.o, savedState.rect.size());
564         newState.tabbed = false;
565         newState.tabBar = nullptr;
566     }
567 #endif
568 
569     auto newGapPos = newState.gapIndex(mousePos, nestingEnabled, tabMode);
570     Q_ASSERT(!newGapPos.isEmpty());
571     if (newGapPos == currentGapPos)
572         return false; // gap is already there
573     currentGapPos = newGapPos;
574     newState.insertGap(currentGapPos, widgetItem);
575     newState.fitItems();
576     *layoutInfo() = std::move(newState);
577     updateCurrentGapRect();
578     layoutInfo()->apply(opts & QMainWindow::AnimatedDocks);
579     return true;
580 }
581 
updateCurrentGapRect()582 void QDockWidgetGroupWindow::updateCurrentGapRect()
583 {
584     if (!currentGapPos.isEmpty())
585         currentGapRect = layoutInfo()->info(currentGapPos)->itemRect(currentGapPos.last(), true);
586 }
587 
588 /*
589     Remove the gap that was created by hover()
590  */
restore()591 void QDockWidgetGroupWindow::restore()
592 {
593     QDockAreaLayoutInfo &savedState = static_cast<QDockWidgetGroupLayout *>(layout())->savedState;
594     if (!savedState.isEmpty()) {
595         *layoutInfo() = savedState;
596         savedState = QDockAreaLayoutInfo();
597     }
598     currentGapRect = QRect();
599     currentGapPos.clear();
600     adjustFlags();
601     layoutInfo()->fitItems();
602     layoutInfo()->apply(static_cast<QMainWindow *>(parentWidget())->dockOptions()
603                         & QMainWindow::AnimatedDocks);
604 }
605 
606 /*
607     Apply the state  that was created by hover
608  */
apply()609 void QDockWidgetGroupWindow::apply()
610 {
611     static_cast<QDockWidgetGroupLayout *>(layout())->savedState.clear();
612     currentGapRect = QRect();
613     layoutInfo()->plug(currentGapPos);
614     currentGapPos.clear();
615     adjustFlags();
616     layoutInfo()->apply(false);
617 }
618 
619 #endif
620 
621 /******************************************************************************
622 ** QMainWindowLayoutState
623 */
624 
625 // we deal with all the #ifndefferry here so QMainWindowLayout code is clean
626 
QMainWindowLayoutState(QMainWindow * win)627 QMainWindowLayoutState::QMainWindowLayoutState(QMainWindow *win)
628     :
629 #if QT_CONFIG(toolbar)
630     toolBarAreaLayout(win),
631 #endif
632 #if QT_CONFIG(dockwidget)
633     dockAreaLayout(win)
634 #else
635     centralWidgetItem(0)
636 #endif
637 
638 {
639     mainWindow = win;
640 }
641 
sizeHint() const642 QSize QMainWindowLayoutState::sizeHint() const
643 {
644 
645     QSize result(0, 0);
646 
647 #if QT_CONFIG(dockwidget)
648     result = dockAreaLayout.sizeHint();
649 #else
650     if (centralWidgetItem != 0)
651         result = centralWidgetItem->sizeHint();
652 #endif
653 
654 #if QT_CONFIG(toolbar)
655     result = toolBarAreaLayout.sizeHint(result);
656 #endif // QT_CONFIG(toolbar)
657 
658     return result;
659 }
660 
minimumSize() const661 QSize QMainWindowLayoutState::minimumSize() const
662 {
663     QSize result(0, 0);
664 
665 #if QT_CONFIG(dockwidget)
666     result = dockAreaLayout.minimumSize();
667 #else
668     if (centralWidgetItem != 0)
669         result = centralWidgetItem->minimumSize();
670 #endif
671 
672 #if QT_CONFIG(toolbar)
673     result = toolBarAreaLayout.minimumSize(result);
674 #endif // QT_CONFIG(toolbar)
675 
676     return result;
677 }
678 
apply(bool animated)679 void QMainWindowLayoutState::apply(bool animated)
680 {
681 #if QT_CONFIG(toolbar)
682     toolBarAreaLayout.apply(animated);
683 #endif
684 
685 #if QT_CONFIG(dockwidget)
686 //    dumpLayout(dockAreaLayout, QString());
687     dockAreaLayout.apply(animated);
688 #else
689     if (centralWidgetItem != 0) {
690         QMainWindowLayout *layout = qt_mainwindow_layout(mainWindow);
691         Q_ASSERT(layout != 0);
692         layout->widgetAnimator.animate(centralWidgetItem->widget(), centralWidgetRect, animated);
693     }
694 #endif
695 }
696 
fitLayout()697 void QMainWindowLayoutState::fitLayout()
698 {
699     QRect r;
700 #if !QT_CONFIG(toolbar)
701     r = rect;
702 #else
703     toolBarAreaLayout.rect = rect;
704     r = toolBarAreaLayout.fitLayout();
705 #endif // QT_CONFIG(toolbar)
706 
707 #if QT_CONFIG(dockwidget)
708     dockAreaLayout.rect = r;
709     dockAreaLayout.fitLayout();
710 #else
711     centralWidgetRect = r;
712 #endif
713 }
714 
deleteAllLayoutItems()715 void QMainWindowLayoutState::deleteAllLayoutItems()
716 {
717 #if QT_CONFIG(toolbar)
718     toolBarAreaLayout.deleteAllLayoutItems();
719 #endif
720 
721 #if QT_CONFIG(dockwidget)
722     dockAreaLayout.deleteAllLayoutItems();
723 #endif
724 }
725 
deleteCentralWidgetItem()726 void QMainWindowLayoutState::deleteCentralWidgetItem()
727 {
728 #if QT_CONFIG(dockwidget)
729     delete dockAreaLayout.centralWidgetItem;
730     dockAreaLayout.centralWidgetItem = nullptr;
731 #else
732     delete centralWidgetItem;
733     centralWidgetItem = 0;
734 #endif
735 }
736 
itemAt(int index,int * x) const737 QLayoutItem *QMainWindowLayoutState::itemAt(int index, int *x) const
738 {
739 #if QT_CONFIG(toolbar)
740     if (QLayoutItem *ret = toolBarAreaLayout.itemAt(x, index))
741         return ret;
742 #endif
743 
744 #if QT_CONFIG(dockwidget)
745     if (QLayoutItem *ret = dockAreaLayout.itemAt(x, index))
746         return ret;
747 #else
748     if (centralWidgetItem != 0 && (*x)++ == index)
749         return centralWidgetItem;
750 #endif
751 
752     return nullptr;
753 }
754 
takeAt(int index,int * x)755 QLayoutItem *QMainWindowLayoutState::takeAt(int index, int *x)
756 {
757 #if QT_CONFIG(toolbar)
758     if (QLayoutItem *ret = toolBarAreaLayout.takeAt(x, index))
759         return ret;
760 #endif
761 
762 #if QT_CONFIG(dockwidget)
763     if (QLayoutItem *ret = dockAreaLayout.takeAt(x, index))
764         return ret;
765 #else
766     if (centralWidgetItem != 0 && (*x)++ == index) {
767         QLayoutItem *ret = centralWidgetItem;
768         centralWidgetItem = 0;
769         return ret;
770     }
771 #endif
772 
773     return nullptr;
774 }
775 
indexOf(QWidget * widget) const776 QList<int> QMainWindowLayoutState::indexOf(QWidget *widget) const
777 {
778     QList<int> result;
779 
780 #if QT_CONFIG(toolbar)
781     // is it a toolbar?
782     if (QToolBar *toolBar = qobject_cast<QToolBar*>(widget)) {
783         result = toolBarAreaLayout.indexOf(toolBar);
784         if (!result.isEmpty())
785             result.prepend(0);
786         return result;
787     }
788 #endif
789 
790 #if QT_CONFIG(dockwidget)
791     // is it a dock widget?
792     if (qobject_cast<QDockWidget *>(widget) || qobject_cast<QDockWidgetGroupWindow *>(widget)) {
793         result = dockAreaLayout.indexOf(widget);
794         if (!result.isEmpty())
795             result.prepend(1);
796         return result;
797     }
798 #endif // QT_CONFIG(dockwidget)
799 
800     return result;
801 }
802 
contains(QWidget * widget) const803 bool QMainWindowLayoutState::contains(QWidget *widget) const
804 {
805 #if QT_CONFIG(dockwidget)
806     if (dockAreaLayout.centralWidgetItem != nullptr && dockAreaLayout.centralWidgetItem->widget() == widget)
807         return true;
808     if (!dockAreaLayout.indexOf(widget).isEmpty())
809         return true;
810 #else
811     if (centralWidgetItem != 0 && centralWidgetItem->widget() == widget)
812         return true;
813 #endif
814 
815 #if QT_CONFIG(toolbar)
816     if (!toolBarAreaLayout.indexOf(widget).isEmpty())
817         return true;
818 #endif
819     return false;
820 }
821 
setCentralWidget(QWidget * widget)822 void QMainWindowLayoutState::setCentralWidget(QWidget *widget)
823 {
824     QLayoutItem *item = nullptr;
825     //make sure we remove the widget
826     deleteCentralWidgetItem();
827 
828     if (widget != nullptr)
829         item = new QWidgetItemV2(widget);
830 
831 #if QT_CONFIG(dockwidget)
832     dockAreaLayout.centralWidgetItem = item;
833 #else
834     centralWidgetItem = item;
835 #endif
836 }
837 
centralWidget() const838 QWidget *QMainWindowLayoutState::centralWidget() const
839 {
840     QLayoutItem *item = nullptr;
841 
842 #if QT_CONFIG(dockwidget)
843     item = dockAreaLayout.centralWidgetItem;
844 #else
845     item = centralWidgetItem;
846 #endif
847 
848     if (item != nullptr)
849         return item->widget();
850     return nullptr;
851 }
852 
gapIndex(QWidget * widget,const QPoint & pos) const853 QList<int> QMainWindowLayoutState::gapIndex(QWidget *widget,
854                                             const QPoint &pos) const
855 {
856     QList<int> result;
857 
858 #if QT_CONFIG(toolbar)
859     // is it a toolbar?
860     if (qobject_cast<QToolBar*>(widget) != 0) {
861         result = toolBarAreaLayout.gapIndex(pos);
862         if (!result.isEmpty())
863             result.prepend(0);
864         return result;
865     }
866 #endif
867 
868 #if QT_CONFIG(dockwidget)
869     // is it a dock widget?
870     if (qobject_cast<QDockWidget *>(widget) != 0
871             || qobject_cast<QDockWidgetGroupWindow *>(widget)) {
872         bool disallowTabs = false;
873 #if QT_CONFIG(tabbar)
874         if (auto *group = qobject_cast<QDockWidgetGroupWindow *>(widget)) {
875             if (!group->tabLayoutInfo()) // Disallow to drop nested docks as a tab
876                 disallowTabs = true;
877         }
878 #endif
879         result = dockAreaLayout.gapIndex(pos, disallowTabs);
880         if (!result.isEmpty())
881             result.prepend(1);
882         return result;
883     }
884 #endif // QT_CONFIG(dockwidget)
885 
886     return result;
887 }
888 
insertGap(const QList<int> & path,QLayoutItem * item)889 bool QMainWindowLayoutState::insertGap(const QList<int> &path, QLayoutItem *item)
890 {
891     if (path.isEmpty())
892         return false;
893 
894     int i = path.first();
895 
896 #if QT_CONFIG(toolbar)
897     if (i == 0) {
898         Q_ASSERT(qobject_cast<QToolBar*>(item->widget()) != 0);
899         return toolBarAreaLayout.insertGap(path.mid(1), item);
900     }
901 #endif
902 
903 #if QT_CONFIG(dockwidget)
904     if (i == 1) {
905         Q_ASSERT(qobject_cast<QDockWidget*>(item->widget()) || qobject_cast<QDockWidgetGroupWindow*>(item->widget()));
906         return dockAreaLayout.insertGap(path.mid(1), item);
907     }
908 #endif // QT_CONFIG(dockwidget)
909 
910     return false;
911 }
912 
remove(const QList<int> & path)913 void QMainWindowLayoutState::remove(const QList<int> &path)
914 {
915     int i = path.first();
916 
917 #if QT_CONFIG(toolbar)
918     if (i == 0)
919         toolBarAreaLayout.remove(path.mid(1));
920 #endif
921 
922 #if QT_CONFIG(dockwidget)
923     if (i == 1)
924         dockAreaLayout.remove(path.mid(1));
925 #endif // QT_CONFIG(dockwidget)
926 }
927 
remove(QLayoutItem * item)928 void QMainWindowLayoutState::remove(QLayoutItem *item)
929 {
930 #if QT_CONFIG(toolbar)
931     toolBarAreaLayout.remove(item);
932 #endif
933 
934 #if QT_CONFIG(dockwidget)
935     // is it a dock widget?
936     if (QDockWidget *dockWidget = qobject_cast<QDockWidget *>(item->widget())) {
937         QList<int> path = dockAreaLayout.indexOf(dockWidget);
938         if (!path.isEmpty())
939             dockAreaLayout.remove(path);
940     }
941 #endif // QT_CONFIG(dockwidget)
942 }
943 
clear()944 void QMainWindowLayoutState::clear()
945 {
946 #if QT_CONFIG(toolbar)
947     toolBarAreaLayout.clear();
948 #endif
949 
950 #if QT_CONFIG(dockwidget)
951     dockAreaLayout.clear();
952 #else
953     centralWidgetRect = QRect();
954 #endif
955 
956     rect = QRect();
957 }
958 
isValid() const959 bool QMainWindowLayoutState::isValid() const
960 {
961     return rect.isValid();
962 }
963 
item(const QList<int> & path)964 QLayoutItem *QMainWindowLayoutState::item(const QList<int> &path)
965 {
966     int i = path.first();
967 
968 #if QT_CONFIG(toolbar)
969     if (i == 0) {
970         const QToolBarAreaLayoutItem *tbItem = toolBarAreaLayout.item(path.mid(1));
971         Q_ASSERT(tbItem);
972         return tbItem->widgetItem;
973     }
974 #endif
975 
976 #if QT_CONFIG(dockwidget)
977     if (i == 1)
978         return dockAreaLayout.item(path.mid(1)).widgetItem;
979 #endif // QT_CONFIG(dockwidget)
980 
981     return nullptr;
982 }
983 
itemRect(const QList<int> & path) const984 QRect QMainWindowLayoutState::itemRect(const QList<int> &path) const
985 {
986     int i = path.first();
987 
988 #if QT_CONFIG(toolbar)
989     if (i == 0)
990         return toolBarAreaLayout.itemRect(path.mid(1));
991 #endif
992 
993 #if QT_CONFIG(dockwidget)
994     if (i == 1)
995         return dockAreaLayout.itemRect(path.mid(1));
996 #endif // QT_CONFIG(dockwidget)
997 
998     return QRect();
999 }
1000 
gapRect(const QList<int> & path) const1001 QRect QMainWindowLayoutState::gapRect(const QList<int> &path) const
1002 {
1003     int i = path.first();
1004 
1005 #if QT_CONFIG(toolbar)
1006     if (i == 0)
1007         return toolBarAreaLayout.itemRect(path.mid(1));
1008 #endif
1009 
1010 #if QT_CONFIG(dockwidget)
1011     if (i == 1)
1012         return dockAreaLayout.gapRect(path.mid(1));
1013 #endif // QT_CONFIG(dockwidget)
1014 
1015     return QRect();
1016 }
1017 
plug(const QList<int> & path)1018 QLayoutItem *QMainWindowLayoutState::plug(const QList<int> &path)
1019 {
1020     int i = path.first();
1021 
1022 #if QT_CONFIG(toolbar)
1023     if (i == 0)
1024         return toolBarAreaLayout.plug(path.mid(1));
1025 #endif
1026 
1027 #if QT_CONFIG(dockwidget)
1028     if (i == 1)
1029         return dockAreaLayout.plug(path.mid(1));
1030 #endif // QT_CONFIG(dockwidget)
1031 
1032     return nullptr;
1033 }
1034 
unplug(const QList<int> & path,QMainWindowLayoutState * other)1035 QLayoutItem *QMainWindowLayoutState::unplug(const QList<int> &path, QMainWindowLayoutState *other)
1036 {
1037     int i = path.first();
1038 
1039 #if !QT_CONFIG(toolbar)
1040     Q_UNUSED(other);
1041 #else
1042     if (i == 0)
1043         return toolBarAreaLayout.unplug(path.mid(1), other ? &other->toolBarAreaLayout : nullptr);
1044 #endif
1045 
1046 #if QT_CONFIG(dockwidget)
1047     if (i == 1)
1048         return dockAreaLayout.unplug(path.mid(1));
1049 #endif // QT_CONFIG(dockwidget)
1050 
1051     return nullptr;
1052 }
1053 
saveState(QDataStream & stream) const1054 void QMainWindowLayoutState::saveState(QDataStream &stream) const
1055 {
1056 #if QT_CONFIG(dockwidget)
1057     dockAreaLayout.saveState(stream);
1058 #if QT_CONFIG(tabbar)
1059     const QList<QDockWidgetGroupWindow *> floatingTabs =
1060         mainWindow->findChildren<QDockWidgetGroupWindow *>(QString(), Qt::FindDirectChildrenOnly);
1061 
1062     for (QDockWidgetGroupWindow *floating : floatingTabs) {
1063         if (floating->layoutInfo()->isEmpty())
1064             continue;
1065         stream << uchar(QDockAreaLayout::FloatingDockWidgetTabMarker) << floating->geometry();
1066         floating->layoutInfo()->saveState(stream);
1067     }
1068 #endif
1069 #endif
1070 #if QT_CONFIG(toolbar)
1071     toolBarAreaLayout.saveState(stream);
1072 #endif
1073 }
1074 
1075 template <typename T>
findChildrenHelper(const QObject * o)1076 static QList<T> findChildrenHelper(const QObject *o)
1077 {
1078     const QObjectList &list = o->children();
1079     QList<T> result;
1080 
1081     for (int i=0; i < list.size(); ++i) {
1082         if (T t = qobject_cast<T>(list[i])) {
1083             result.append(t);
1084         }
1085     }
1086 
1087     return result;
1088 }
1089 
1090 #if QT_CONFIG(dockwidget)
allMyDockWidgets(const QWidget * mainWindow)1091 static QList<QDockWidget*> allMyDockWidgets(const QWidget *mainWindow)
1092 {
1093     QList<QDockWidget*> result;
1094     for (QObject *c : mainWindow->children()) {
1095         if (auto *dw = qobject_cast<QDockWidget*>(c)) {
1096             result.append(dw);
1097         } else if (auto *gw = qobject_cast<QDockWidgetGroupWindow*>(c)) {
1098             for (QObject *c : gw->children()) {
1099                 if (auto *dw = qobject_cast<QDockWidget*>(c))
1100                     result.append(dw);
1101             }
1102         }
1103     }
1104 
1105     return result;
1106 }
1107 #endif // QT_CONFIG(dockwidget)
1108 
1109 //pre4.3 tests the format that was used before 4.3
checkFormat(QDataStream & stream)1110 bool QMainWindowLayoutState::checkFormat(QDataStream &stream)
1111 {
1112     while (!stream.atEnd()) {
1113         uchar marker;
1114         stream >> marker;
1115         switch(marker)
1116         {
1117 #if QT_CONFIG(toolbar)
1118             case QToolBarAreaLayout::ToolBarStateMarker:
1119             case QToolBarAreaLayout::ToolBarStateMarkerEx:
1120                 {
1121                     QList<QToolBar *> toolBars = findChildrenHelper<QToolBar*>(mainWindow);
1122                     if (!toolBarAreaLayout.restoreState(stream, toolBars, marker, true /*testing*/)) {
1123                             return false;
1124                     }
1125                 }
1126                 break;
1127 #endif // QT_CONFIG(toolbar)
1128 
1129 #if QT_CONFIG(dockwidget)
1130             case QDockAreaLayout::DockWidgetStateMarker:
1131                 {
1132                     const auto dockWidgets = allMyDockWidgets(mainWindow);
1133                     if (!dockAreaLayout.restoreState(stream, dockWidgets, true /*testing*/)) {
1134                         return false;
1135                     }
1136                 }
1137                 break;
1138 #if QT_CONFIG(tabbar)
1139             case QDockAreaLayout::FloatingDockWidgetTabMarker:
1140                 {
1141                     QRect geom;
1142                     stream >> geom;
1143                     QDockAreaLayoutInfo info;
1144                     auto dockWidgets = allMyDockWidgets(mainWindow);
1145                     if (!info.restoreState(stream, dockWidgets, true /* testing*/))
1146                         return false;
1147                 }
1148                 break;
1149 #endif // QT_CONFIG(tabbar)
1150 #endif // QT_CONFIG(dockwidget)
1151             default:
1152                 //there was an error during the parsing
1153                 return false;
1154         }// switch
1155     } //while
1156 
1157     //everything went fine: it must be a pre-4.3 saved state
1158     return true;
1159 }
1160 
restoreState(QDataStream & _stream,const QMainWindowLayoutState & oldState)1161 bool QMainWindowLayoutState::restoreState(QDataStream &_stream,
1162                                         const QMainWindowLayoutState &oldState)
1163 {
1164     //make a copy of the data so that we can read it more than once
1165     QByteArray copy;
1166     while(!_stream.atEnd()) {
1167         int length = 1024;
1168         QByteArray ba(length, '\0');
1169         length = _stream.readRawData(ba.data(), ba.size());
1170         ba.resize(length);
1171         copy += ba;
1172     }
1173 
1174     QDataStream ds(copy);
1175     if (!checkFormat(ds))
1176         return false;
1177 
1178     QDataStream stream(copy);
1179 
1180     while (!stream.atEnd()) {
1181         uchar marker;
1182         stream >> marker;
1183         switch(marker)
1184         {
1185 #if QT_CONFIG(dockwidget)
1186             case QDockAreaLayout::DockWidgetStateMarker:
1187                 {
1188                     const auto dockWidgets = allMyDockWidgets(mainWindow);
1189                     if (!dockAreaLayout.restoreState(stream, dockWidgets))
1190                         return false;
1191 
1192                     for (int i = 0; i < dockWidgets.size(); ++i) {
1193                         QDockWidget *w = dockWidgets.at(i);
1194                         QList<int> path = dockAreaLayout.indexOf(w);
1195                         if (path.isEmpty()) {
1196                             QList<int> oldPath = oldState.dockAreaLayout.indexOf(w);
1197                             if (oldPath.isEmpty()) {
1198                                 continue;
1199                             }
1200                             QDockAreaLayoutInfo *info = dockAreaLayout.info(oldPath);
1201                             if (info == nullptr) {
1202                                 continue;
1203                             }
1204                             info->item_list.append(QDockAreaLayoutItem(new QDockWidgetItem(w)));
1205                         }
1206                     }
1207                 }
1208                 break;
1209 #if QT_CONFIG(tabwidget)
1210             case QDockAreaLayout::FloatingDockWidgetTabMarker:
1211             {
1212                 auto dockWidgets = allMyDockWidgets(mainWindow);
1213                 QDockWidgetGroupWindow* floatingTab = qt_mainwindow_layout(mainWindow)->createTabbedDockWindow();
1214                 *floatingTab->layoutInfo() = QDockAreaLayoutInfo(&dockAreaLayout.sep, QInternal::LeftDock,
1215                                                                  Qt::Horizontal, QTabBar::RoundedSouth, mainWindow);
1216                 QRect geometry;
1217                 stream >> geometry;
1218                 QDockAreaLayoutInfo *info = floatingTab->layoutInfo();
1219                 if (!info->restoreState(stream, dockWidgets, false))
1220                     return false;
1221                 geometry = QDockAreaLayout::constrainedRect(geometry, floatingTab);
1222                 floatingTab->move(geometry.topLeft());
1223                 floatingTab->resize(geometry.size());
1224 
1225                 // Don't show an empty QDockWidgetGroupWindow if no dock widget is available yet.
1226                 // reparentWidgets() would be triggered by show(), so do it explicitly here.
1227                 if (info->onlyHasPlaceholders())
1228                     info->reparentWidgets(floatingTab);
1229                 else
1230                     floatingTab->show();
1231             }
1232             break;
1233 #endif // QT_CONFIG(tabwidget)
1234 #endif // QT_CONFIG(dockwidget)
1235 
1236 #if QT_CONFIG(toolbar)
1237             case QToolBarAreaLayout::ToolBarStateMarker:
1238             case QToolBarAreaLayout::ToolBarStateMarkerEx:
1239                 {
1240                     QList<QToolBar *> toolBars = findChildrenHelper<QToolBar*>(mainWindow);
1241                     if (!toolBarAreaLayout.restoreState(stream, toolBars, marker))
1242                         return false;
1243 
1244                     for (int i = 0; i < toolBars.size(); ++i) {
1245                         QToolBar *w = toolBars.at(i);
1246                         QList<int> path = toolBarAreaLayout.indexOf(w);
1247                         if (path.isEmpty()) {
1248                             QList<int> oldPath = oldState.toolBarAreaLayout.indexOf(w);
1249                             if (oldPath.isEmpty()) {
1250                                 continue;
1251                             }
1252                             toolBarAreaLayout.docks[oldPath.at(0)].insertToolBar(nullptr, w);
1253                         }
1254                     }
1255                 }
1256                 break;
1257 #endif // QT_CONFIG(toolbar)
1258             default:
1259                 return false;
1260         }// switch
1261     } //while
1262 
1263 
1264     return true;
1265 }
1266 
1267 /******************************************************************************
1268 ** QMainWindowLayoutState - toolbars
1269 */
1270 
1271 #if QT_CONFIG(toolbar)
1272 
validateToolBarArea(Qt::ToolBarArea & area)1273 static inline void validateToolBarArea(Qt::ToolBarArea &area)
1274 {
1275     switch (area) {
1276     case Qt::LeftToolBarArea:
1277     case Qt::RightToolBarArea:
1278     case Qt::TopToolBarArea:
1279     case Qt::BottomToolBarArea:
1280         break;
1281     default:
1282         area = Qt::TopToolBarArea;
1283     }
1284 }
1285 
toDockPos(Qt::ToolBarArea area)1286 static QInternal::DockPosition toDockPos(Qt::ToolBarArea area)
1287 {
1288     switch (area) {
1289         case Qt::LeftToolBarArea: return QInternal::LeftDock;
1290         case Qt::RightToolBarArea: return QInternal::RightDock;
1291         case Qt::TopToolBarArea: return QInternal::TopDock;
1292         case Qt::BottomToolBarArea: return QInternal::BottomDock;
1293         default:
1294             break;
1295     }
1296 
1297     return QInternal::DockCount;
1298 }
1299 
toToolBarArea(QInternal::DockPosition pos)1300 static Qt::ToolBarArea toToolBarArea(QInternal::DockPosition pos)
1301 {
1302     switch (pos) {
1303         case QInternal::LeftDock:   return Qt::LeftToolBarArea;
1304         case QInternal::RightDock:  return Qt::RightToolBarArea;
1305         case QInternal::TopDock:    return Qt::TopToolBarArea;
1306         case QInternal::BottomDock: return Qt::BottomToolBarArea;
1307         default: break;
1308     }
1309     return Qt::NoToolBarArea;
1310 }
1311 
toToolBarArea(int pos)1312 static inline Qt::ToolBarArea toToolBarArea(int pos)
1313 {
1314     return toToolBarArea(static_cast<QInternal::DockPosition>(pos));
1315 }
1316 
addToolBarBreak(Qt::ToolBarArea area)1317 void QMainWindowLayout::addToolBarBreak(Qt::ToolBarArea area)
1318 {
1319     validateToolBarArea(area);
1320 
1321     layoutState.toolBarAreaLayout.addToolBarBreak(toDockPos(area));
1322     if (savedState.isValid())
1323         savedState.toolBarAreaLayout.addToolBarBreak(toDockPos(area));
1324 
1325     invalidate();
1326 }
1327 
insertToolBarBreak(QToolBar * before)1328 void QMainWindowLayout::insertToolBarBreak(QToolBar *before)
1329 {
1330     layoutState.toolBarAreaLayout.insertToolBarBreak(before);
1331     if (savedState.isValid())
1332         savedState.toolBarAreaLayout.insertToolBarBreak(before);
1333     invalidate();
1334 }
1335 
removeToolBarBreak(QToolBar * before)1336 void QMainWindowLayout::removeToolBarBreak(QToolBar *before)
1337 {
1338     layoutState.toolBarAreaLayout.removeToolBarBreak(before);
1339     if (savedState.isValid())
1340         savedState.toolBarAreaLayout.removeToolBarBreak(before);
1341     invalidate();
1342 }
1343 
moveToolBar(QToolBar * toolbar,int pos)1344 void QMainWindowLayout::moveToolBar(QToolBar *toolbar, int pos)
1345 {
1346     layoutState.toolBarAreaLayout.moveToolBar(toolbar, pos);
1347     if (savedState.isValid())
1348         savedState.toolBarAreaLayout.moveToolBar(toolbar, pos);
1349     invalidate();
1350 }
1351 
1352 /* Removes the toolbar from the mainwindow so that it can be added again. Does not
1353    explicitly hide the toolbar. */
removeToolBar(QToolBar * toolbar)1354 void QMainWindowLayout::removeToolBar(QToolBar *toolbar)
1355 {
1356     if (toolbar) {
1357         QObject::disconnect(parentWidget(), SIGNAL(iconSizeChanged(QSize)),
1358                    toolbar, SLOT(_q_updateIconSize(QSize)));
1359         QObject::disconnect(parentWidget(), SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)),
1360                    toolbar, SLOT(_q_updateToolButtonStyle(Qt::ToolButtonStyle)));
1361 
1362         removeWidget(toolbar);
1363     }
1364 }
1365 
1366 /*!
1367     Adds \a toolbar to \a area, continuing the current line.
1368 */
addToolBar(Qt::ToolBarArea area,QToolBar * toolbar,bool)1369 void QMainWindowLayout::addToolBar(Qt::ToolBarArea area,
1370                                    QToolBar *toolbar,
1371                                    bool)
1372 {
1373     validateToolBarArea(area);
1374     // let's add the toolbar to the layout
1375     addChildWidget(toolbar);
1376     QLayoutItem *item = layoutState.toolBarAreaLayout.addToolBar(toDockPos(area), toolbar);
1377     if (savedState.isValid() && item) {
1378         // copy the toolbar also in the saved state
1379         savedState.toolBarAreaLayout.insertItem(toDockPos(area), item);
1380     }
1381     invalidate();
1382 
1383     // this ensures that the toolbar has the right window flags (not floating any more)
1384     toolbar->d_func()->updateWindowFlags(false /*floating*/);
1385 }
1386 
1387 /*!
1388     Adds \a toolbar before \a before
1389 */
insertToolBar(QToolBar * before,QToolBar * toolbar)1390 void QMainWindowLayout::insertToolBar(QToolBar *before, QToolBar *toolbar)
1391 {
1392     addChildWidget(toolbar);
1393     QLayoutItem *item = layoutState.toolBarAreaLayout.insertToolBar(before, toolbar);
1394     if (savedState.isValid() && item) {
1395         // copy the toolbar also in the saved state
1396         savedState.toolBarAreaLayout.insertItem(before, item);
1397     }
1398     if (!currentGapPos.isEmpty() && currentGapPos.constFirst() == 0) {
1399         currentGapPos = layoutState.toolBarAreaLayout.currentGapIndex();
1400         if (!currentGapPos.isEmpty()) {
1401             currentGapPos.prepend(0);
1402             currentGapRect = layoutState.itemRect(currentGapPos);
1403         }
1404     }
1405     invalidate();
1406 }
1407 
toolBarArea(const QToolBar * toolbar) const1408 Qt::ToolBarArea QMainWindowLayout::toolBarArea(const QToolBar *toolbar) const
1409 {
1410     QInternal::DockPosition pos = layoutState.toolBarAreaLayout.findToolBar(toolbar);
1411     switch (pos) {
1412         case QInternal::LeftDock:   return Qt::LeftToolBarArea;
1413         case QInternal::RightDock:  return Qt::RightToolBarArea;
1414         case QInternal::TopDock:    return Qt::TopToolBarArea;
1415         case QInternal::BottomDock: return Qt::BottomToolBarArea;
1416         default: break;
1417     }
1418     return Qt::NoToolBarArea;
1419 }
1420 
toolBarBreak(QToolBar * toolBar) const1421 bool QMainWindowLayout::toolBarBreak(QToolBar *toolBar) const
1422 {
1423     return layoutState.toolBarAreaLayout.toolBarBreak(toolBar);
1424 }
1425 
getStyleOptionInfo(QStyleOptionToolBar * option,QToolBar * toolBar) const1426 void QMainWindowLayout::getStyleOptionInfo(QStyleOptionToolBar *option, QToolBar *toolBar) const
1427 {
1428     option->toolBarArea = toolBarArea(toolBar);
1429     layoutState.toolBarAreaLayout.getStyleOptionInfo(option, toolBar);
1430 }
1431 
toggleToolBarsVisible()1432 void QMainWindowLayout::toggleToolBarsVisible()
1433 {
1434     layoutState.toolBarAreaLayout.visible = !layoutState.toolBarAreaLayout.visible;
1435     if (!layoutState.mainWindow->isMaximized()) {
1436         QPoint topLeft = parentWidget()->geometry().topLeft();
1437         QRect r = parentWidget()->geometry();
1438         r = layoutState.toolBarAreaLayout.rectHint(r);
1439         r.moveTo(topLeft);
1440         parentWidget()->setGeometry(r);
1441     } else {
1442         update();
1443     }
1444 }
1445 
1446 #endif // QT_CONFIG(toolbar)
1447 
1448 /******************************************************************************
1449 ** QMainWindowLayoutState - dock areas
1450 */
1451 
1452 #if QT_CONFIG(dockwidget)
1453 
toDockPos(Qt::DockWidgetArea area)1454 static QInternal::DockPosition toDockPos(Qt::DockWidgetArea area)
1455 {
1456     switch (area) {
1457         case Qt::LeftDockWidgetArea: return QInternal::LeftDock;
1458         case Qt::RightDockWidgetArea: return QInternal::RightDock;
1459         case Qt::TopDockWidgetArea: return QInternal::TopDock;
1460         case Qt::BottomDockWidgetArea: return QInternal::BottomDock;
1461         default:
1462             break;
1463     }
1464 
1465     return QInternal::DockCount;
1466 }
1467 
toDockWidgetArea(QInternal::DockPosition pos)1468 static Qt::DockWidgetArea toDockWidgetArea(QInternal::DockPosition pos)
1469 {
1470     switch (pos) {
1471         case QInternal::LeftDock : return Qt::LeftDockWidgetArea;
1472         case QInternal::RightDock : return Qt::RightDockWidgetArea;
1473         case QInternal::TopDock : return Qt::TopDockWidgetArea;
1474         case QInternal::BottomDock : return Qt::BottomDockWidgetArea;
1475         default:
1476             break;
1477     }
1478 
1479     return Qt::NoDockWidgetArea;
1480 }
1481 
toDockWidgetArea(int pos)1482 inline static Qt::DockWidgetArea toDockWidgetArea(int pos)
1483 {
1484     return toDockWidgetArea(static_cast<QInternal::DockPosition>(pos));
1485 }
1486 
setCorner(Qt::Corner corner,Qt::DockWidgetArea area)1487 void QMainWindowLayout::setCorner(Qt::Corner corner, Qt::DockWidgetArea area)
1488 {
1489     if (layoutState.dockAreaLayout.corners[corner] == area)
1490         return;
1491     layoutState.dockAreaLayout.corners[corner] = area;
1492     if (savedState.isValid())
1493         savedState.dockAreaLayout.corners[corner] = area;
1494     invalidate();
1495 }
1496 
corner(Qt::Corner corner) const1497 Qt::DockWidgetArea QMainWindowLayout::corner(Qt::Corner corner) const
1498 {
1499     return layoutState.dockAreaLayout.corners[corner];
1500 }
1501 
addDockWidget(Qt::DockWidgetArea area,QDockWidget * dockwidget,Qt::Orientation orientation)1502 void QMainWindowLayout::addDockWidget(Qt::DockWidgetArea area,
1503                                              QDockWidget *dockwidget,
1504                                              Qt::Orientation orientation)
1505 {
1506     addChildWidget(dockwidget);
1507 
1508     // If we are currently moving a separator, then we need to abort the move, since each
1509     // time we move the mouse layoutState is replaced by savedState modified by the move.
1510     if (!movingSeparator.isEmpty())
1511         endSeparatorMove(movingSeparatorPos);
1512 
1513     layoutState.dockAreaLayout.addDockWidget(toDockPos(area), dockwidget, orientation);
1514     emit dockwidget->dockLocationChanged(area);
1515     invalidate();
1516 }
1517 
restoreDockWidget(QDockWidget * dockwidget)1518 bool QMainWindowLayout::restoreDockWidget(QDockWidget *dockwidget)
1519 {
1520     addChildWidget(dockwidget);
1521     if (!layoutState.dockAreaLayout.restoreDockWidget(dockwidget))
1522         return false;
1523     emit dockwidget->dockLocationChanged(dockWidgetArea(dockwidget));
1524     invalidate();
1525     return true;
1526 }
1527 
1528 #if QT_CONFIG(tabbar)
tabifyDockWidget(QDockWidget * first,QDockWidget * second)1529 void QMainWindowLayout::tabifyDockWidget(QDockWidget *first, QDockWidget *second)
1530 {
1531     addChildWidget(second);
1532     layoutState.dockAreaLayout.tabifyDockWidget(first, second);
1533     emit second->dockLocationChanged(dockWidgetArea(first));
1534     invalidate();
1535 }
1536 
documentMode() const1537 bool QMainWindowLayout::documentMode() const
1538 {
1539     return _documentMode;
1540 }
1541 
setDocumentMode(bool enabled)1542 void QMainWindowLayout::setDocumentMode(bool enabled)
1543 {
1544     if (_documentMode == enabled)
1545         return;
1546 
1547     _documentMode = enabled;
1548 
1549     // Update the document mode for all tab bars
1550     for (QTabBar *bar : qAsConst(usedTabBars))
1551         bar->setDocumentMode(_documentMode);
1552     for (QTabBar *bar : qAsConst(unusedTabBars))
1553         bar->setDocumentMode(_documentMode);
1554 }
1555 
setVerticalTabsEnabled(bool enabled)1556 void QMainWindowLayout::setVerticalTabsEnabled(bool enabled)
1557 {
1558     if (verticalTabsEnabled == enabled)
1559         return;
1560 
1561     verticalTabsEnabled = enabled;
1562 
1563     updateTabBarShapes();
1564 }
1565 
1566 #if QT_CONFIG(tabwidget)
tabShape() const1567 QTabWidget::TabShape QMainWindowLayout::tabShape() const
1568 {
1569     return _tabShape;
1570 }
1571 
setTabShape(QTabWidget::TabShape tabShape)1572 void QMainWindowLayout::setTabShape(QTabWidget::TabShape tabShape)
1573 {
1574     if (_tabShape == tabShape)
1575         return;
1576 
1577     _tabShape = tabShape;
1578 
1579     updateTabBarShapes();
1580 }
1581 
tabPosition(Qt::DockWidgetArea area) const1582 QTabWidget::TabPosition QMainWindowLayout::tabPosition(Qt::DockWidgetArea area) const
1583 {
1584     const auto dockPos = toDockPos(area);
1585     if (dockPos < QInternal::DockCount)
1586         return tabPositions[dockPos];
1587     qWarning("QMainWindowLayout::tabPosition called with out-of-bounds value '%d'", int(area));
1588     return QTabWidget::North;
1589 }
1590 
setTabPosition(Qt::DockWidgetAreas areas,QTabWidget::TabPosition tabPosition)1591 void QMainWindowLayout::setTabPosition(Qt::DockWidgetAreas areas, QTabWidget::TabPosition tabPosition)
1592 {
1593     const Qt::DockWidgetArea dockWidgetAreas[] = {
1594         Qt::TopDockWidgetArea,
1595         Qt::LeftDockWidgetArea,
1596         Qt::BottomDockWidgetArea,
1597         Qt::RightDockWidgetArea
1598     };
1599     const QInternal::DockPosition dockPositions[] = {
1600         QInternal::TopDock,
1601         QInternal::LeftDock,
1602         QInternal::BottomDock,
1603         QInternal::RightDock
1604     };
1605 
1606     for (int i = 0; i < QInternal::DockCount; ++i)
1607         if (areas & dockWidgetAreas[i])
1608             tabPositions[dockPositions[i]] = tabPosition;
1609 
1610     updateTabBarShapes();
1611 }
1612 
tabBarShapeFrom(QTabWidget::TabShape shape,QTabWidget::TabPosition position)1613 static inline QTabBar::Shape tabBarShapeFrom(QTabWidget::TabShape shape, QTabWidget::TabPosition position)
1614 {
1615     const bool rounded = (shape == QTabWidget::Rounded);
1616     if (position == QTabWidget::North)
1617         return rounded ? QTabBar::RoundedNorth : QTabBar::TriangularNorth;
1618     if (position == QTabWidget::South)
1619         return rounded ? QTabBar::RoundedSouth : QTabBar::TriangularSouth;
1620     if (position == QTabWidget::East)
1621         return rounded ? QTabBar::RoundedEast : QTabBar::TriangularEast;
1622     if (position == QTabWidget::West)
1623         return rounded ? QTabBar::RoundedWest : QTabBar::TriangularWest;
1624     return QTabBar::RoundedNorth;
1625 }
1626 #endif // QT_CONFIG(tabwidget)
1627 
updateTabBarShapes()1628 void QMainWindowLayout::updateTabBarShapes()
1629 {
1630 #if QT_CONFIG(tabwidget)
1631     const QTabWidget::TabPosition vertical[] = {
1632         QTabWidget::West,
1633         QTabWidget::East,
1634         QTabWidget::North,
1635         QTabWidget::South
1636     };
1637 #else
1638     const QTabBar::Shape vertical[] = {
1639         QTabBar::RoundedWest,
1640         QTabBar::RoundedEast,
1641         QTabBar::RoundedNorth,
1642         QTabBar::RoundedSouth
1643     };
1644 #endif
1645 
1646     QDockAreaLayout &layout = layoutState.dockAreaLayout;
1647 
1648     for (int i = 0; i < QInternal::DockCount; ++i) {
1649 #if QT_CONFIG(tabwidget)
1650         QTabWidget::TabPosition pos = verticalTabsEnabled ? vertical[i] : tabPositions[i];
1651         QTabBar::Shape shape = tabBarShapeFrom(_tabShape, pos);
1652 #else
1653         QTabBar::Shape shape = verticalTabsEnabled ? vertical[i] : QTabBar::RoundedSouth;
1654 #endif
1655         layout.docks[i].setTabBarShape(shape);
1656     }
1657 }
1658 #endif // QT_CONFIG(tabbar)
1659 
splitDockWidget(QDockWidget * after,QDockWidget * dockwidget,Qt::Orientation orientation)1660 void QMainWindowLayout::splitDockWidget(QDockWidget *after,
1661                                         QDockWidget *dockwidget,
1662                                         Qt::Orientation orientation)
1663 {
1664     addChildWidget(dockwidget);
1665     layoutState.dockAreaLayout.splitDockWidget(after, dockwidget, orientation);
1666     emit dockwidget->dockLocationChanged(dockWidgetArea(after));
1667     invalidate();
1668 }
1669 
dockWidgetArea(QWidget * widget) const1670 Qt::DockWidgetArea QMainWindowLayout::dockWidgetArea(QWidget *widget) const
1671 {
1672     const QList<int> pathToWidget = layoutState.dockAreaLayout.indexOf(widget);
1673     if (pathToWidget.isEmpty())
1674         return Qt::NoDockWidgetArea;
1675     return toDockWidgetArea(pathToWidget.first());
1676 }
1677 
keepSize(QDockWidget * w)1678 void QMainWindowLayout::keepSize(QDockWidget *w)
1679 {
1680     layoutState.dockAreaLayout.keepSize(w);
1681 }
1682 
1683 #if QT_CONFIG(tabbar)
1684 
1685 // Handle custom tooltip, and allow to drag tabs away.
1686 class QMainWindowTabBar : public QTabBar
1687 {
1688     QMainWindow *mainWindow;
1689     QPointer<QDockWidget> draggingDock; // Currently dragging (detached) dock widget
1690 public:
1691     QMainWindowTabBar(QMainWindow *parent);
1692 protected:
1693     bool event(QEvent *e) override;
1694     void mouseReleaseEvent(QMouseEvent*) override;
1695     void mouseMoveEvent(QMouseEvent*) override;
1696 
1697 };
1698 
QMainWindowTabBar(QMainWindow * parent)1699 QMainWindowTabBar::QMainWindowTabBar(QMainWindow *parent)
1700     : QTabBar(parent), mainWindow(parent)
1701 {
1702     setExpanding(false);
1703 }
1704 
mouseMoveEvent(QMouseEvent * e)1705 void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e)
1706 {
1707     // The QTabBar handles the moving (reordering) of tabs.
1708     // When QTabBarPrivate::dragInProgress is true, and that the mouse is outside of a region
1709     // around the QTabBar, we will consider the user wants to drag that QDockWidget away from this
1710     // tab area.
1711 
1712     QTabBarPrivate *d = static_cast<QTabBarPrivate*>(d_ptr.data());
1713     if (!draggingDock && (mainWindow->dockOptions() & QMainWindow::GroupedDragging)) {
1714         int offset = QApplication::startDragDistance() + 1;
1715         offset *= 3;
1716         QRect r = rect().adjusted(-offset, -offset, offset, offset);
1717         if (d->dragInProgress && !r.contains(e->pos()) && d->validIndex(d->pressedIndex)) {
1718             QMainWindowLayout* mlayout = qt_mainwindow_layout(mainWindow);
1719             QDockAreaLayoutInfo *info = mlayout->dockInfo(this);
1720             Q_ASSERT(info);
1721             int idx = info->tabIndexToListIndex(d->pressedIndex);
1722             const QDockAreaLayoutItem &item = info->item_list.at(idx);
1723             if (item.widgetItem
1724                     && (draggingDock = qobject_cast<QDockWidget *>(item.widgetItem->widget()))) {
1725                 // We should drag this QDockWidget away by unpluging it.
1726                 // First cancel the QTabBar's internal move
1727                 d->moveTabFinished(d->pressedIndex);
1728                 d->pressedIndex = -1;
1729                 if (d->movingTab)
1730                     d->movingTab->setVisible(false);
1731                 d->dragStartPosition = QPoint();
1732 
1733                 // Then starts the drag using QDockWidgetPrivate's API
1734                 QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock));
1735                 QDockWidgetLayout *dwlayout = static_cast<QDockWidgetLayout *>(draggingDock->layout());
1736                 dockPriv->initDrag(dwlayout->titleArea().center(), true);
1737                 dockPriv->startDrag(false);
1738                 if (dockPriv->state)
1739                     dockPriv->state->ctrlDrag = e->modifiers() & Qt::ControlModifier;
1740             }
1741         }
1742     }
1743 
1744     if (draggingDock) {
1745         QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock));
1746         if (dockPriv->state && dockPriv->state->dragging) {
1747             QPoint pos = e->globalPos() - dockPriv->state->pressPos;
1748             draggingDock->move(pos);
1749             // move will call QMainWindowLayout::hover
1750         }
1751     }
1752     QTabBar::mouseMoveEvent(e);
1753 }
1754 
mouseReleaseEvent(QMouseEvent * e)1755 void QMainWindowTabBar::mouseReleaseEvent(QMouseEvent *e)
1756 {
1757     if (draggingDock && e->button() == Qt::LeftButton) {
1758         QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock));
1759         if (dockPriv->state && dockPriv->state->dragging) {
1760             dockPriv->endDrag();
1761         }
1762         draggingDock = nullptr;
1763     }
1764     QTabBar::mouseReleaseEvent(e);
1765 }
1766 
event(QEvent * e)1767 bool QMainWindowTabBar::event(QEvent *e)
1768 {
1769     // show the tooltip if tab is too small to fit label
1770 
1771     if (e->type() != QEvent::ToolTip)
1772         return QTabBar::event(e);
1773     QSize size = this->size();
1774     QSize hint = sizeHint();
1775     if (shape() == QTabBar::RoundedWest || shape() == QTabBar::RoundedEast) {
1776         size = size.transposed();
1777         hint = hint.transposed();
1778     }
1779     if (size.width() < hint.width())
1780         return QTabBar::event(e);
1781     e->accept();
1782     return true;
1783 }
1784 
getTabBar()1785 QTabBar *QMainWindowLayout::getTabBar()
1786 {
1787     if (!usedTabBars.isEmpty()) {
1788         /*
1789             If dock widgets have been removed and added while the main window was
1790             hidden, then the layout hasn't been activated yet, and tab bars from empty
1791             docking areas haven't been put in the cache yet.
1792         */
1793         activate();
1794     }
1795 
1796     QTabBar *result = nullptr;
1797     if (!unusedTabBars.isEmpty()) {
1798         result = unusedTabBars.takeLast();
1799     } else {
1800         result = new QMainWindowTabBar(static_cast<QMainWindow *>(parentWidget()));
1801         result->setDrawBase(true);
1802         result->setElideMode(Qt::ElideRight);
1803         result->setDocumentMode(_documentMode);
1804         result->setMovable(true);
1805         connect(result, SIGNAL(currentChanged(int)), this, SLOT(tabChanged()));
1806         connect(result, &QTabBar::tabMoved, this, &QMainWindowLayout::tabMoved);
1807     }
1808 
1809     usedTabBars.insert(result);
1810     return result;
1811 }
1812 
1813 // Allocates a new separator widget if needed
getSeparatorWidget()1814 QWidget *QMainWindowLayout::getSeparatorWidget()
1815 {
1816     QWidget *result = nullptr;
1817     if (!unusedSeparatorWidgets.isEmpty()) {
1818         result = unusedSeparatorWidgets.takeLast();
1819     } else {
1820         result = new QWidget(parentWidget());
1821         result->setAttribute(Qt::WA_MouseNoMask, true);
1822         result->setAutoFillBackground(false);
1823         result->setObjectName(QLatin1String("qt_qmainwindow_extended_splitter"));
1824     }
1825     usedSeparatorWidgets.insert(result);
1826     return result;
1827 }
1828 
1829 /*! \internal
1830     Returns a pointer QDockAreaLayoutInfo which contains this \a widget directly
1831     (in its internal list)
1832  */
dockInfo(QWidget * widget)1833 QDockAreaLayoutInfo *QMainWindowLayout::dockInfo(QWidget *widget)
1834 {
1835     QDockAreaLayoutInfo *info = layoutState.dockAreaLayout.info(widget);
1836     if (info)
1837         return info;
1838     const auto groups =
1839             parent()->findChildren<QDockWidgetGroupWindow*>(QString(), Qt::FindDirectChildrenOnly);
1840     for (QDockWidgetGroupWindow *dwgw : groups) {
1841         info = dwgw->layoutInfo()->info(widget);
1842         if (info)
1843             return info;
1844     }
1845     return nullptr;
1846 }
1847 
tabChanged()1848 void QMainWindowLayout::tabChanged()
1849 {
1850     QTabBar *tb = qobject_cast<QTabBar*>(sender());
1851     if (tb == nullptr)
1852         return;
1853     QDockAreaLayoutInfo *info = dockInfo(tb);
1854     if (info == nullptr)
1855         return;
1856 
1857     QDockWidget *activated = info->apply(false);
1858 
1859     if (activated)
1860         emit static_cast<QMainWindow *>(parentWidget())->tabifiedDockWidgetActivated(activated);
1861 
1862     if (auto dwgw = qobject_cast<QDockWidgetGroupWindow*>(tb->parentWidget()))
1863         dwgw->adjustFlags();
1864 
1865     if (QWidget *w = centralWidget())
1866         w->raise();
1867 }
1868 
tabMoved(int from,int to)1869 void QMainWindowLayout::tabMoved(int from, int to)
1870 {
1871     QTabBar *tb = qobject_cast<QTabBar*>(sender());
1872     Q_ASSERT(tb);
1873     QDockAreaLayoutInfo *info = dockInfo(tb);
1874     Q_ASSERT(info);
1875 
1876     info->moveTab(from, to);
1877 }
1878 
raise(QDockWidget * widget)1879 void QMainWindowLayout::raise(QDockWidget *widget)
1880 {
1881     QDockAreaLayoutInfo *info = dockInfo(widget);
1882     if (info == nullptr)
1883         return;
1884     if (!info->tabbed)
1885         return;
1886     info->setCurrentTab(widget);
1887 }
1888 #endif // QT_CONFIG(tabbar)
1889 
1890 #endif // QT_CONFIG(dockwidget)
1891 
1892 
1893 /******************************************************************************
1894 ** QMainWindowLayoutState - layout interface
1895 */
1896 
count() const1897 int QMainWindowLayout::count() const
1898 {
1899     qWarning("QMainWindowLayout::count: ?");
1900     return 0; //#################################################
1901 }
1902 
itemAt(int index) const1903 QLayoutItem *QMainWindowLayout::itemAt(int index) const
1904 {
1905     int x = 0;
1906 
1907     if (QLayoutItem *ret = layoutState.itemAt(index, &x))
1908         return ret;
1909 
1910     if (statusbar && x++ == index)
1911         return statusbar;
1912 
1913     return nullptr;
1914 }
1915 
takeAt(int index)1916 QLayoutItem *QMainWindowLayout::takeAt(int index)
1917 {
1918     int x = 0;
1919 
1920     if (QLayoutItem *ret = layoutState.takeAt(index, &x)) {
1921         // the widget might in fact have been destroyed by now
1922         if (QWidget *w = ret->widget()) {
1923             widgetAnimator.abort(w);
1924             if (w == pluggingWidget)
1925                 pluggingWidget = nullptr;
1926         }
1927 
1928         if (savedState.isValid() ) {
1929             //we need to remove the item also from the saved state to prevent crash
1930             savedState.remove(ret);
1931             //Also, the item may be contained several times as a gap item.
1932             layoutState.remove(ret);
1933         }
1934 
1935 #if QT_CONFIG(toolbar)
1936         if (!currentGapPos.isEmpty() && currentGapPos.constFirst() == 0) {
1937             currentGapPos = layoutState.toolBarAreaLayout.currentGapIndex();
1938             if (!currentGapPos.isEmpty()) {
1939                 currentGapPos.prepend(0);
1940                 currentGapRect = layoutState.itemRect(currentGapPos);
1941             }
1942         }
1943 #endif
1944 
1945         return ret;
1946     }
1947 
1948     if (statusbar && x++ == index) {
1949         QLayoutItem *ret = statusbar;
1950         statusbar = nullptr;
1951         return ret;
1952     }
1953 
1954     return nullptr;
1955 }
1956 
setGeometry(const QRect & _r)1957 void QMainWindowLayout::setGeometry(const QRect &_r)
1958 {
1959     if (savedState.isValid())
1960         return;
1961 
1962     QRect r = _r;
1963 
1964     QLayout::setGeometry(r);
1965 
1966     if (statusbar) {
1967         QRect sbr(QPoint(r.left(), 0),
1968                   QSize(r.width(), statusbar->heightForWidth(r.width()))
1969                   .expandedTo(statusbar->minimumSize()));
1970         sbr.moveBottom(r.bottom());
1971         QRect vr = QStyle::visualRect(parentWidget()->layoutDirection(), _r, sbr);
1972         statusbar->setGeometry(vr);
1973         r.setBottom(sbr.top() - 1);
1974     }
1975 
1976     layoutState.rect = r;
1977     layoutState.fitLayout();
1978     applyState(layoutState, false);
1979 }
1980 
addItem(QLayoutItem *)1981 void QMainWindowLayout::addItem(QLayoutItem *)
1982 { qWarning("QMainWindowLayout::addItem: Please use the public QMainWindow API instead"); }
1983 
sizeHint() const1984 QSize QMainWindowLayout::sizeHint() const
1985 {
1986     if (!szHint.isValid()) {
1987         szHint = layoutState.sizeHint();
1988         const QSize sbHint = statusbar ? statusbar->sizeHint() : QSize(0, 0);
1989         szHint = QSize(qMax(sbHint.width(), szHint.width()),
1990                         sbHint.height() + szHint.height());
1991     }
1992     return szHint;
1993 }
1994 
minimumSize() const1995 QSize QMainWindowLayout::minimumSize() const
1996 {
1997     if (!minSize.isValid()) {
1998         minSize = layoutState.minimumSize();
1999         const QSize sbMin = statusbar ? statusbar->minimumSize() : QSize(0, 0);
2000         minSize = QSize(qMax(sbMin.width(), minSize.width()),
2001                         sbMin.height() + minSize.height());
2002     }
2003     return minSize;
2004 }
2005 
invalidate()2006 void QMainWindowLayout::invalidate()
2007 {
2008     QLayout::invalidate();
2009     minSize = szHint = QSize();
2010 }
2011 
2012 #if QT_CONFIG(dockwidget)
setCurrentHoveredFloat(QDockWidgetGroupWindow * w)2013 void QMainWindowLayout::setCurrentHoveredFloat(QDockWidgetGroupWindow *w)
2014 {
2015     if (currentHoveredFloat != w) {
2016         if (currentHoveredFloat) {
2017             disconnect(currentHoveredFloat.data(), &QObject::destroyed,
2018                        this, &QMainWindowLayout::updateGapIndicator);
2019             disconnect(currentHoveredFloat.data(), &QDockWidgetGroupWindow::resized,
2020                        this, &QMainWindowLayout::updateGapIndicator);
2021             if (currentHoveredFloat)
2022                 currentHoveredFloat->restore();
2023         } else if (w) {
2024             restore(true);
2025         }
2026 
2027         currentHoveredFloat = w;
2028 
2029         if (w) {
2030             connect(w, &QObject::destroyed,
2031                     this, &QMainWindowLayout::updateGapIndicator, Qt::UniqueConnection);
2032             connect(w, &QDockWidgetGroupWindow::resized,
2033                     this, &QMainWindowLayout::updateGapIndicator, Qt::UniqueConnection);
2034         }
2035 
2036         updateGapIndicator();
2037     }
2038 }
2039 #endif // QT_CONFIG(dockwidget)
2040 
2041 /******************************************************************************
2042 ** QMainWindowLayout - remaining stuff
2043 */
2044 
fixToolBarOrientation(QLayoutItem * item,int dockPos)2045 static void fixToolBarOrientation(QLayoutItem *item, int dockPos)
2046 {
2047 #if QT_CONFIG(toolbar)
2048     QToolBar *toolBar = qobject_cast<QToolBar*>(item->widget());
2049     if (toolBar == nullptr)
2050         return;
2051 
2052     QRect oldGeo = toolBar->geometry();
2053 
2054     QInternal::DockPosition pos
2055         = static_cast<QInternal::DockPosition>(dockPos);
2056     Qt::Orientation o = pos == QInternal::TopDock || pos == QInternal::BottomDock
2057                         ? Qt::Horizontal : Qt::Vertical;
2058     if (o != toolBar->orientation())
2059         toolBar->setOrientation(o);
2060 
2061     QSize hint = toolBar->sizeHint().boundedTo(toolBar->maximumSize())
2062                     .expandedTo(toolBar->minimumSize());
2063 
2064     if (toolBar->size() != hint) {
2065         QRect newGeo(oldGeo.topLeft(), hint);
2066         if (toolBar->layoutDirection() == Qt::RightToLeft)
2067             newGeo.moveRight(oldGeo.right());
2068         toolBar->setGeometry(newGeo);
2069     }
2070 
2071 #else
2072     Q_UNUSED(item);
2073     Q_UNUSED(dockPos);
2074 #endif
2075 }
2076 
revert(QLayoutItem * widgetItem)2077 void QMainWindowLayout::revert(QLayoutItem *widgetItem)
2078 {
2079     if (!savedState.isValid())
2080         return;
2081 
2082     QWidget *widget = widgetItem->widget();
2083     layoutState = savedState;
2084     currentGapPos = layoutState.indexOf(widget);
2085     if (currentGapPos.isEmpty())
2086         return;
2087     fixToolBarOrientation(widgetItem, currentGapPos.at(1));
2088     layoutState.unplug(currentGapPos);
2089     layoutState.fitLayout();
2090     currentGapRect = layoutState.itemRect(currentGapPos);
2091 
2092     plug(widgetItem);
2093 }
2094 
plug(QLayoutItem * widgetItem)2095 bool QMainWindowLayout::plug(QLayoutItem *widgetItem)
2096 {
2097 #if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget) && QT_CONFIG(tabbar)
2098     if (currentHoveredFloat) {
2099         QWidget *widget = widgetItem->widget();
2100         QList<int> previousPath = layoutState.indexOf(widget);
2101         if (!previousPath.isEmpty())
2102             layoutState.remove(previousPath);
2103         previousPath = currentHoveredFloat->layoutInfo()->indexOf(widget);
2104         // Let's remove the widget from any possible group window
2105         const auto groups =
2106                 parent()->findChildren<QDockWidgetGroupWindow*>(QString(), Qt::FindDirectChildrenOnly);
2107         for (QDockWidgetGroupWindow *dwgw : groups) {
2108             if (dwgw == currentHoveredFloat)
2109                 continue;
2110             QList<int> path = dwgw->layoutInfo()->indexOf(widget);
2111             if (!path.isEmpty())
2112                 dwgw->layoutInfo()->remove(path);
2113         }
2114         currentGapRect = QRect();
2115         currentHoveredFloat->apply();
2116         if (!previousPath.isEmpty())
2117             currentHoveredFloat->layoutInfo()->remove(previousPath);
2118         QRect globalRect = currentHoveredFloat->currentGapRect;
2119         globalRect.moveTopLeft(currentHoveredFloat->mapToGlobal(globalRect.topLeft()));
2120         pluggingWidget = widget;
2121         widgetAnimator.animate(widget, globalRect, dockOptions & QMainWindow::AnimatedDocks);
2122         return true;
2123     }
2124 #endif
2125 
2126     if (!parentWidget()->isVisible() || parentWidget()->isMinimized() || currentGapPos.isEmpty())
2127         return false;
2128 
2129     fixToolBarOrientation(widgetItem, currentGapPos.at(1));
2130 
2131     QWidget *widget = widgetItem->widget();
2132 
2133 #if QT_CONFIG(dockwidget)
2134     // Let's remove the widget from any possible group window
2135     const auto groups =
2136             parent()->findChildren<QDockWidgetGroupWindow*>(QString(), Qt::FindDirectChildrenOnly);
2137     for (QDockWidgetGroupWindow *dwgw : groups) {
2138         QList<int> path = dwgw->layoutInfo()->indexOf(widget);
2139         if (!path.isEmpty())
2140             dwgw->layoutInfo()->remove(path);
2141     }
2142 #endif
2143 
2144     QList<int> previousPath = layoutState.indexOf(widget);
2145 
2146     const QLayoutItem *it = layoutState.plug(currentGapPos);
2147     if (!it)
2148         return false;
2149     Q_ASSERT(it == widgetItem);
2150     if (!previousPath.isEmpty())
2151         layoutState.remove(previousPath);
2152 
2153     pluggingWidget = widget;
2154     QRect globalRect = currentGapRect;
2155     globalRect.moveTopLeft(parentWidget()->mapToGlobal(globalRect.topLeft()));
2156 #if QT_CONFIG(dockwidget)
2157     if (qobject_cast<QDockWidget*>(widget) != 0) {
2158         QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(widget->layout());
2159         if (layout->nativeWindowDeco()) {
2160             globalRect.adjust(0, layout->titleHeight(), 0, 0);
2161         } else {
2162             int fw = widget->style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, nullptr, widget);
2163             globalRect.adjust(-fw, -fw, fw, fw);
2164         }
2165     }
2166 #endif
2167     widgetAnimator.animate(widget, globalRect, dockOptions & QMainWindow::AnimatedDocks);
2168 
2169     return true;
2170 }
2171 
animationFinished(QWidget * widget)2172 void QMainWindowLayout::animationFinished(QWidget *widget)
2173 {
2174     //this function is called from within the Widget Animator whenever an animation is finished
2175     //on a certain widget
2176 #if QT_CONFIG(toolbar)
2177     if (QToolBar *tb = qobject_cast<QToolBar*>(widget)) {
2178         QToolBarLayout *tbl = qobject_cast<QToolBarLayout*>(tb->layout());
2179         if (tbl->animating) {
2180             tbl->animating = false;
2181             if (tbl->expanded)
2182                 tbl->layoutActions(tb->size());
2183             tb->update();
2184         }
2185     }
2186 #endif
2187 
2188     if (widget == pluggingWidget) {
2189 
2190 #if QT_CONFIG(dockwidget)
2191 #if QT_CONFIG(tabbar)
2192         if (QDockWidgetGroupWindow *dwgw = qobject_cast<QDockWidgetGroupWindow *>(widget)) {
2193             // When the animated widget was a QDockWidgetGroupWindow, it means each of the
2194             // embedded QDockWidget needs to be plugged back into the QMainWindow layout.
2195             savedState.clear();
2196             QDockAreaLayoutInfo *srcInfo = dwgw->layoutInfo();
2197             const QDockAreaLayoutInfo *srcTabInfo = dwgw->tabLayoutInfo();
2198             QDockAreaLayoutInfo *dstParentInfo;
2199             QList<int> dstPath;
2200 
2201             if (currentHoveredFloat) {
2202                 dstPath = currentHoveredFloat->layoutInfo()->indexOf(widget);
2203                 Q_ASSERT(dstPath.size() >= 1);
2204                 dstParentInfo = currentHoveredFloat->layoutInfo()->info(dstPath);
2205             } else {
2206                 dstPath = layoutState.dockAreaLayout.indexOf(widget);
2207                 Q_ASSERT(dstPath.size() >= 2);
2208                 dstParentInfo = layoutState.dockAreaLayout.info(dstPath);
2209             }
2210             Q_ASSERT(dstParentInfo);
2211             int idx = dstPath.constLast();
2212             Q_ASSERT(dstParentInfo->item_list[idx].widgetItem->widget() == dwgw);
2213             if (dstParentInfo->tabbed && srcTabInfo) {
2214                 // merge the two tab widgets
2215                 delete dstParentInfo->item_list[idx].widgetItem;
2216                 dstParentInfo->item_list.removeAt(idx);
2217                 std::copy(srcTabInfo->item_list.cbegin(), srcTabInfo->item_list.cend(),
2218                           std::inserter(dstParentInfo->item_list,
2219                                         dstParentInfo->item_list.begin() + idx));
2220                 quintptr currentId = srcTabInfo->currentTabId();
2221                 *srcInfo = QDockAreaLayoutInfo();
2222                 dstParentInfo->reparentWidgets(currentHoveredFloat ? currentHoveredFloat.data()
2223                                                                    : parentWidget());
2224                 dstParentInfo->updateTabBar();
2225                 dstParentInfo->setCurrentTabId(currentId);
2226             } else {
2227                 QDockAreaLayoutItem &item = dstParentInfo->item_list[idx];
2228                 Q_ASSERT(item.widgetItem->widget() == dwgw);
2229                 delete item.widgetItem;
2230                 item.widgetItem = nullptr;
2231                 item.subinfo = new QDockAreaLayoutInfo(std::move(*srcInfo));
2232                 *srcInfo = QDockAreaLayoutInfo();
2233                 item.subinfo->reparentWidgets(currentHoveredFloat ? currentHoveredFloat.data()
2234                                                                   : parentWidget());
2235                 item.subinfo->setTabBarShape(dstParentInfo->tabBarShape);
2236             }
2237             dwgw->destroyOrHideIfEmpty();
2238         }
2239 #endif
2240 
2241         if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) {
2242             dw->setParent(currentHoveredFloat ? currentHoveredFloat.data() : parentWidget());
2243             dw->show();
2244             dw->d_func()->plug(currentGapRect);
2245         }
2246 #endif
2247 #if QT_CONFIG(toolbar)
2248         if (QToolBar *tb = qobject_cast<QToolBar*>(widget))
2249             tb->d_func()->plug(currentGapRect);
2250 #endif
2251 
2252         savedState.clear();
2253         currentGapPos.clear();
2254         pluggingWidget = nullptr;
2255 #if QT_CONFIG(dockwidget)
2256         setCurrentHoveredFloat(nullptr);
2257 #endif
2258         //applying the state will make sure that the currentGap is updated correctly
2259         //and all the geometries (especially the one from the central widget) is correct
2260         layoutState.apply(false);
2261 
2262 #if QT_CONFIG(dockwidget)
2263 #if QT_CONFIG(tabbar)
2264         if (qobject_cast<QDockWidget*>(widget) != 0) {
2265             // info() might return null if the widget is destroyed while
2266             // animating but before the animationFinished signal is received.
2267             if (QDockAreaLayoutInfo *info = dockInfo(widget))
2268                 info->setCurrentTab(widget);
2269         }
2270 #endif
2271 #endif
2272     }
2273 
2274     if (!widgetAnimator.animating()) {
2275         //all animations are finished
2276 #if QT_CONFIG(dockwidget)
2277         parentWidget()->update(layoutState.dockAreaLayout.separatorRegion());
2278 #if QT_CONFIG(tabbar)
2279         const auto usedTabBarsCopy = usedTabBars; // list potentially modified by animations
2280         for (QTabBar *tab_bar : usedTabBarsCopy)
2281             tab_bar->show();
2282 #endif // QT_CONFIG(tabbar)
2283 #endif // QT_CONFIG(dockwidget)
2284     }
2285 
2286     updateGapIndicator();
2287 }
2288 
restore(bool keepSavedState)2289 void QMainWindowLayout::restore(bool keepSavedState)
2290 {
2291     if (!savedState.isValid())
2292         return;
2293 
2294     layoutState = savedState;
2295     applyState(layoutState);
2296     if (!keepSavedState)
2297         savedState.clear();
2298     currentGapPos.clear();
2299     pluggingWidget = nullptr;
2300     updateGapIndicator();
2301 }
2302 
QMainWindowLayout(QMainWindow * mainwindow,QLayout * parentLayout)2303 QMainWindowLayout::QMainWindowLayout(QMainWindow *mainwindow, QLayout *parentLayout)
2304     : QLayout(parentLayout ? static_cast<QWidget *>(nullptr) : mainwindow)
2305     , layoutState(mainwindow)
2306     , savedState(mainwindow)
2307     , dockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowTabbedDocks)
2308     , statusbar(nullptr)
2309 #if QT_CONFIG(dockwidget)
2310 #if QT_CONFIG(tabbar)
2311     , _documentMode(false)
2312     , verticalTabsEnabled(false)
2313 #if QT_CONFIG(tabwidget)
2314     , _tabShape(QTabWidget::Rounded)
2315 #endif
2316 #endif
2317 #endif // QT_CONFIG(dockwidget)
2318     , widgetAnimator(this)
2319     , pluggingWidget(nullptr)
2320 {
2321     if (parentLayout)
2322         setParent(parentLayout);
2323 
2324 #if QT_CONFIG(dockwidget)
2325 #if QT_CONFIG(tabbar)
2326     sep = mainwindow->style()->pixelMetric(QStyle::PM_DockWidgetSeparatorExtent, nullptr, mainwindow);
2327 #endif
2328 
2329 #if QT_CONFIG(tabwidget)
2330     for (int i = 0; i < QInternal::DockCount; ++i)
2331         tabPositions[i] = QTabWidget::South;
2332 #endif
2333 #endif // QT_CONFIG(dockwidget)
2334     pluggingWidget = nullptr;
2335 
2336     setObjectName(mainwindow->objectName() + QLatin1String("_layout"));
2337 }
2338 
~QMainWindowLayout()2339 QMainWindowLayout::~QMainWindowLayout()
2340 {
2341     layoutState.deleteAllLayoutItems();
2342     layoutState.deleteCentralWidgetItem();
2343 
2344     delete statusbar;
2345 }
2346 
setDockOptions(QMainWindow::DockOptions opts)2347 void QMainWindowLayout::setDockOptions(QMainWindow::DockOptions opts)
2348 {
2349     if (opts == dockOptions)
2350         return;
2351 
2352     dockOptions = opts;
2353 
2354 #if QT_CONFIG(dockwidget) && QT_CONFIG(tabbar)
2355     setVerticalTabsEnabled(opts & QMainWindow::VerticalTabs);
2356 #endif
2357 
2358     invalidate();
2359 }
2360 
2361 #if QT_CONFIG(statusbar)
statusBar() const2362 QStatusBar *QMainWindowLayout::statusBar() const
2363 { return statusbar ? qobject_cast<QStatusBar *>(statusbar->widget()) : 0; }
2364 
setStatusBar(QStatusBar * sb)2365 void QMainWindowLayout::setStatusBar(QStatusBar *sb)
2366 {
2367     if (sb)
2368         addChildWidget(sb);
2369     delete statusbar;
2370     statusbar = sb ? new QWidgetItemV2(sb) : nullptr;
2371     invalidate();
2372 }
2373 #endif // QT_CONFIG(statusbar)
2374 
centralWidget() const2375 QWidget *QMainWindowLayout::centralWidget() const
2376 {
2377     return layoutState.centralWidget();
2378 }
2379 
setCentralWidget(QWidget * widget)2380 void QMainWindowLayout::setCentralWidget(QWidget *widget)
2381 {
2382     if (widget != nullptr)
2383         addChildWidget(widget);
2384     layoutState.setCentralWidget(widget);
2385     if (savedState.isValid()) {
2386 #if QT_CONFIG(dockwidget)
2387         savedState.dockAreaLayout.centralWidgetItem = layoutState.dockAreaLayout.centralWidgetItem;
2388         savedState.dockAreaLayout.fallbackToSizeHints = true;
2389 #else
2390         savedState.centralWidgetItem = layoutState.centralWidgetItem;
2391 #endif
2392     }
2393     invalidate();
2394 }
2395 
2396 #if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2397 /*! \internal
2398   This helper function is called by QMainWindowLayout::unplug if QMainWindow::GroupedDragging is
2399   set and we are dragging the title bar of a non-floating QDockWidget.
2400   If one should unplug the whole group, do so and return true, otherwise return false.
2401   \a item is pointing to the QLayoutItem that holds the QDockWidget, but will be updated to the
2402   QLayoutItem that holds the new QDockWidgetGroupWindow if the group is unplugged.
2403 */
unplugGroup(QMainWindowLayout * layout,QLayoutItem ** item,QDockAreaLayoutItem & parentItem)2404 static bool unplugGroup(QMainWindowLayout *layout, QLayoutItem **item,
2405                         QDockAreaLayoutItem &parentItem)
2406 {
2407     if (!parentItem.subinfo || !parentItem.subinfo->tabbed)
2408         return false;
2409 
2410     // The QDockWidget is part of a group of tab and we need to unplug them all.
2411 
2412     QDockWidgetGroupWindow *floatingTabs = layout->createTabbedDockWindow();
2413     QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
2414     *info = std::move(*parentItem.subinfo);
2415     delete parentItem.subinfo;
2416     parentItem.subinfo = nullptr;
2417     floatingTabs->setGeometry(info->rect.translated(layout->parentWidget()->pos()));
2418     floatingTabs->show();
2419     floatingTabs->raise();
2420     *item = new QDockWidgetGroupWindowItem(floatingTabs);
2421     parentItem.widgetItem = *item;
2422     return true;
2423 }
2424 #endif
2425 
2426 /*! \internal
2427     Unplug \a widget (QDockWidget or QToolBar) from it's parent container.
2428 
2429     If \a group is true we might actually unplug the group of tabs this
2430     widget is part if QMainWindow::GroupedDragging is set. When \a group
2431     is false, the widget itself is always unplugged alone
2432 
2433     Returns the QLayoutItem of the dragged element.
2434     The layout item is kept in the layout but set as a gap item.
2435  */
unplug(QWidget * widget,bool group)2436 QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group)
2437 {
2438 #if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2439     auto *groupWindow = qobject_cast<const QDockWidgetGroupWindow *>(widget->parentWidget());
2440     if (!widget->isWindow() && groupWindow) {
2441         if (group && groupWindow->tabLayoutInfo()) {
2442             // We are just dragging a floating window as it, not need to do anything, we just have to
2443             // look up the corresponding QWidgetItem* if it exists
2444             if (QDockAreaLayoutInfo *info = dockInfo(widget->parentWidget())) {
2445                 QList<int> groupWindowPath = info->indexOf(widget->parentWidget());
2446                 return groupWindowPath.isEmpty() ? nullptr : info->item(groupWindowPath).widgetItem;
2447             }
2448             return nullptr;
2449         }
2450         QList<int> path = groupWindow->layoutInfo()->indexOf(widget);
2451         QLayoutItem *item = groupWindow->layoutInfo()->item(path).widgetItem;
2452         if (group && path.size() > 1
2453             && unplugGroup(this, &item,
2454                            groupWindow->layoutInfo()->item(path.mid(0, path.size() - 1)))) {
2455             return item;
2456         } else {
2457             // We are unplugging a dock widget from a floating window.
2458             QDockWidget *dw = qobject_cast<QDockWidget *>(widget);
2459             Q_ASSERT(dw); // cannot be a QDockWidgetGroupWindow because it's not floating.
2460             dw->d_func()->unplug(widget->geometry());
2461             groupWindow->layoutInfo()->fitItems();
2462             groupWindow->layoutInfo()->apply(dockOptions & QMainWindow::AnimatedDocks);
2463             return item;
2464         }
2465     }
2466 #endif
2467     QList<int> path = layoutState.indexOf(widget);
2468     if (path.isEmpty())
2469         return nullptr;
2470 
2471     QLayoutItem *item = layoutState.item(path);
2472     if (widget->isWindow())
2473         return item;
2474 
2475     QRect r = layoutState.itemRect(path);
2476     savedState = layoutState;
2477 
2478 #if QT_CONFIG(dockwidget)
2479     if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) {
2480         Q_ASSERT(path.constFirst() == 1);
2481 #if QT_CONFIG(tabwidget)
2482         if (group && (dockOptions & QMainWindow::GroupedDragging) && path.size() > 3
2483             && unplugGroup(this, &item,
2484                            layoutState.dockAreaLayout.item(path.mid(1, path.size() - 2)))) {
2485             path.removeLast();
2486             savedState = layoutState;
2487         } else
2488 #endif // QT_CONFIG(tabwidget)
2489         {
2490             dw->d_func()->unplug(r);
2491         }
2492     }
2493 #endif // QT_CONFIG(dockwidget)
2494 #if QT_CONFIG(toolbar)
2495     if (QToolBar *tb = qobject_cast<QToolBar*>(widget)) {
2496         tb->d_func()->unplug(r);
2497     }
2498 #endif
2499 
2500 #if !QT_CONFIG(dockwidget) || !QT_CONFIG(tabbar)
2501     Q_UNUSED(group);
2502 #endif
2503 
2504     layoutState.unplug(path ,&savedState);
2505     savedState.fitLayout();
2506     currentGapPos = path;
2507     currentGapRect = r;
2508     updateGapIndicator();
2509 
2510     fixToolBarOrientation(item, currentGapPos.at(1));
2511 
2512     return item;
2513 }
2514 
updateGapIndicator()2515 void QMainWindowLayout::updateGapIndicator()
2516 {
2517 #if QT_CONFIG(rubberband)
2518     if (!widgetAnimator.animating() && (!currentGapPos.isEmpty()
2519 #if QT_CONFIG(dockwidget)
2520                                         || currentHoveredFloat
2521 #endif
2522                                         )) {
2523         QWidget *expectedParent =
2524 #if QT_CONFIG(dockwidget)
2525             currentHoveredFloat ? currentHoveredFloat.data() :
2526 #endif
2527             parentWidget();
2528         if (!gapIndicator) {
2529             gapIndicator = new QRubberBand(QRubberBand::Rectangle, expectedParent);
2530             // For accessibility to identify this special widget.
2531             gapIndicator->setObjectName(QLatin1String("qt_rubberband"));
2532         } else if (gapIndicator->parent() != expectedParent) {
2533             gapIndicator->setParent(expectedParent);
2534         }
2535 
2536 #if QT_CONFIG(dockwidget)
2537         if (currentHoveredFloat)
2538             gapIndicator->setGeometry(currentHoveredFloat->currentGapRect);
2539         else
2540 #endif
2541             gapIndicator->setGeometry(currentGapRect);
2542         gapIndicator->show();
2543         gapIndicator->raise();
2544     } else if (gapIndicator) {
2545         gapIndicator->hide();
2546     }
2547 #endif // QT_CONFIG(rubberband)
2548 }
2549 
2550 #if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
tabwidgetPositionToTabBarShape(QWidget * w)2551 static QTabBar::Shape tabwidgetPositionToTabBarShape(QWidget *w)
2552 {
2553     QTabBar::Shape result = QTabBar::RoundedSouth;
2554     if (qobject_cast<QDockWidget *>(w)) {
2555         switch (static_cast<QDockWidgetPrivate *>(qt_widget_private(w))->tabPosition) {
2556         case QTabWidget::North:
2557             result = QTabBar::RoundedNorth;
2558             break;
2559         case QTabWidget::South:
2560             result = QTabBar::RoundedSouth;
2561             break;
2562         case QTabWidget::West:
2563             result = QTabBar::RoundedWest;
2564             break;
2565         case QTabWidget::East:
2566             result = QTabBar::RoundedEast;
2567             break;
2568         }
2569     }
2570     return result;
2571 }
2572 #endif // QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2573 
hover(QLayoutItem * widgetItem,const QPoint & mousePos)2574 void QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mousePos)
2575 {
2576     if (!parentWidget()->isVisible() || parentWidget()->isMinimized()
2577         || pluggingWidget != nullptr || widgetItem == nullptr)
2578         return;
2579 
2580     QWidget *widget = widgetItem->widget();
2581 
2582 #if QT_CONFIG(dockwidget)
2583     if ((dockOptions & QMainWindow::GroupedDragging) && (qobject_cast<QDockWidget*>(widget)
2584             || qobject_cast<QDockWidgetGroupWindow *>(widget))) {
2585 
2586         // Check if we are over another floating dock widget
2587         QVarLengthArray<QWidget *, 10> candidates;
2588         const auto siblings = parentWidget()->children();
2589         for (QObject *c : siblings) {
2590             QWidget *w = qobject_cast<QWidget*>(c);
2591             if (!w)
2592                 continue;
2593             if (!qobject_cast<QDockWidget*>(w) && !qobject_cast<QDockWidgetGroupWindow *>(w))
2594                 continue;
2595             if (w != widget && w->isTopLevel() && w->isVisible() && !w->isMinimized())
2596                 candidates << w;
2597             if (QDockWidgetGroupWindow *group = qobject_cast<QDockWidgetGroupWindow *>(w)) {
2598                 // Sometimes, there are floating QDockWidget that have a QDockWidgetGroupWindow as a parent.
2599                 const auto groupChildren = group->children();
2600                 for (QObject *c : groupChildren) {
2601                     if (QDockWidget *dw = qobject_cast<QDockWidget*>(c)) {
2602                         if (dw != widget && dw->isFloating() && dw->isVisible() && !dw->isMinimized())
2603                             candidates << dw;
2604                     }
2605                 }
2606             }
2607         }
2608         for (QWidget *w : candidates) {
2609             const QScreen *screen1 = qt_widget_private(widget)->associatedScreen();
2610             const QScreen *screen2 = qt_widget_private(w)->associatedScreen();
2611             if (screen1 && screen2 && screen1 != screen2)
2612                 continue;
2613             if (!w->geometry().contains(mousePos))
2614                 continue;
2615 
2616 #if QT_CONFIG(tabwidget)
2617             if (auto dropTo = qobject_cast<QDockWidget *>(w)) {
2618                 // dropping to a normal widget, we mutate it in a QDockWidgetGroupWindow with two
2619                 // tabs
2620                 QDockWidgetGroupWindow *floatingTabs = createTabbedDockWindow(); // FIXME
2621                 floatingTabs->setGeometry(dropTo->geometry());
2622                 QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
2623                 const QTabBar::Shape shape = tabwidgetPositionToTabBarShape(dropTo);
2624                 *info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, QInternal::LeftDock,
2625                                             Qt::Horizontal, shape,
2626                                             static_cast<QMainWindow *>(parentWidget()));
2627                 info->tabbed = true;
2628                 QLayout *parentLayout = dropTo->parentWidget()->layout();
2629                 info->item_list.append(
2630                     QDockAreaLayoutItem(parentLayout->takeAt(parentLayout->indexOf(dropTo))));
2631 
2632                 dropTo->setParent(floatingTabs);
2633                 dropTo->show();
2634                 dropTo->d_func()->plug(QRect());
2635                 w = floatingTabs;
2636                 widget->raise(); // raise, as our newly created drop target is now on top
2637             }
2638 #endif
2639             Q_ASSERT(qobject_cast<QDockWidgetGroupWindow *>(w));
2640             auto group = static_cast<QDockWidgetGroupWindow *>(w);
2641             if (group->hover(widgetItem, group->mapFromGlobal(mousePos))) {
2642                 setCurrentHoveredFloat(group);
2643                 applyState(layoutState); // update the tabbars
2644             }
2645             return;
2646         }
2647     }
2648     setCurrentHoveredFloat(nullptr);
2649     layoutState.dockAreaLayout.fallbackToSizeHints = false;
2650 #endif // QT_CONFIG(dockwidget)
2651 
2652     QPoint pos = parentWidget()->mapFromGlobal(mousePos);
2653 
2654     if (!savedState.isValid())
2655         savedState = layoutState;
2656 
2657     QList<int> path = savedState.gapIndex(widget, pos);
2658 
2659     if (!path.isEmpty()) {
2660         bool allowed = false;
2661 
2662 #if QT_CONFIG(dockwidget)
2663         if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget))
2664             allowed = dw->isAreaAllowed(toDockWidgetArea(path.at(1)));
2665 
2666         if (qobject_cast<QDockWidgetGroupWindow *>(widget))
2667             allowed = true;
2668 #endif
2669 #if QT_CONFIG(toolbar)
2670         if (QToolBar *tb = qobject_cast<QToolBar*>(widget))
2671             allowed = tb->isAreaAllowed(toToolBarArea(path.at(1)));
2672 #endif
2673 
2674         if (!allowed)
2675             path.clear();
2676     }
2677 
2678     if (path == currentGapPos)
2679         return; // the gap is already there
2680 
2681     currentGapPos = path;
2682     if (path.isEmpty()) {
2683         fixToolBarOrientation(widgetItem, 2); // 2 = top dock, ie. horizontal
2684         restore(true);
2685         return;
2686     }
2687 
2688     fixToolBarOrientation(widgetItem, currentGapPos.at(1));
2689 
2690     QMainWindowLayoutState newState = savedState;
2691 
2692     if (!newState.insertGap(path, widgetItem)) {
2693         restore(true); // not enough space
2694         return;
2695     }
2696 
2697     QSize min = newState.minimumSize();
2698     QSize size = newState.rect.size();
2699 
2700     if (min.width() > size.width() || min.height() > size.height()) {
2701         restore(true);
2702         return;
2703     }
2704 
2705     newState.fitLayout();
2706 
2707     currentGapRect = newState.gapRect(currentGapPos);
2708 
2709 #if QT_CONFIG(dockwidget)
2710     parentWidget()->update(layoutState.dockAreaLayout.separatorRegion());
2711 #endif
2712     layoutState = std::move(newState);
2713     applyState(layoutState);
2714 
2715     updateGapIndicator();
2716 }
2717 
2718 #if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
createTabbedDockWindow()2719 QDockWidgetGroupWindow *QMainWindowLayout::createTabbedDockWindow()
2720 {
2721     QDockWidgetGroupWindow* f = new QDockWidgetGroupWindow(parentWidget(), Qt::Tool);
2722     new QDockWidgetGroupLayout(f);
2723     return f;
2724 }
2725 #endif
2726 
applyState(QMainWindowLayoutState & newState,bool animate)2727 void QMainWindowLayout::applyState(QMainWindowLayoutState &newState, bool animate)
2728 {
2729 #if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2730     QSet<QTabBar*> used = newState.dockAreaLayout.usedTabBars();
2731     const auto groups =
2732             parent()->findChildren<QDockWidgetGroupWindow*>(QString(), Qt::FindDirectChildrenOnly);
2733     for (QDockWidgetGroupWindow *dwgw : groups)
2734         used += dwgw->layoutInfo()->usedTabBars();
2735 
2736     const QSet<QTabBar*> retired = usedTabBars - used;
2737     usedTabBars = used;
2738     for (QTabBar *tab_bar : retired) {
2739         tab_bar->hide();
2740         while (tab_bar->count() > 0)
2741             tab_bar->removeTab(0);
2742         unusedTabBars.append(tab_bar);
2743     }
2744 
2745     if (sep == 1) {
2746         const QSet<QWidget*> usedSeps = newState.dockAreaLayout.usedSeparatorWidgets();
2747         const QSet<QWidget*> retiredSeps = usedSeparatorWidgets - usedSeps;
2748         usedSeparatorWidgets = usedSeps;
2749         for (QWidget *sepWidget : retiredSeps) {
2750             unusedSeparatorWidgets.append(sepWidget);
2751         }
2752     }
2753 
2754     for (int i = 0; i < QInternal::DockCount; ++i)
2755         newState.dockAreaLayout.docks[i].reparentWidgets(parentWidget());
2756 
2757 #endif // QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
2758     newState.apply(dockOptions & QMainWindow::AnimatedDocks && animate);
2759 }
2760 
saveState(QDataStream & stream) const2761 void QMainWindowLayout::saveState(QDataStream &stream) const
2762 {
2763     layoutState.saveState(stream);
2764 }
2765 
restoreState(QDataStream & stream)2766 bool QMainWindowLayout::restoreState(QDataStream &stream)
2767 {
2768     savedState = layoutState;
2769     layoutState.clear();
2770     layoutState.rect = savedState.rect;
2771 
2772     if (!layoutState.restoreState(stream, savedState)) {
2773         layoutState.deleteAllLayoutItems();
2774         layoutState = savedState;
2775         if (parentWidget()->isVisible())
2776             applyState(layoutState, false); // hides tabBars allocated by newState
2777         return false;
2778     }
2779 
2780     if (parentWidget()->isVisible()) {
2781         layoutState.fitLayout();
2782         applyState(layoutState, false);
2783     }
2784 
2785     savedState.deleteAllLayoutItems();
2786     savedState.clear();
2787 
2788 #if QT_CONFIG(dockwidget)
2789     if (parentWidget()->isVisible()) {
2790 #if QT_CONFIG(tabbar)
2791         for (QTabBar *tab_bar : qAsConst(usedTabBars))
2792             tab_bar->show();
2793 
2794 #endif
2795     }
2796 #endif // QT_CONFIG(dockwidget)
2797 
2798     return true;
2799 }
2800 
2801 QT_END_NAMESPACE
2802 
2803 #include "moc_qmainwindowlayout_p.cpp"
2804