summaryrefslogtreecommitdiffstats
path: root/src/widgets/widgets/qmainwindowlayout.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/widgets/widgets/qmainwindowlayout.cpp')
-rw-r--r--src/widgets/widgets/qmainwindowlayout.cpp926
1 files changed, 699 insertions, 227 deletions
diff --git a/src/widgets/widgets/qmainwindowlayout.cpp b/src/widgets/widgets/qmainwindowlayout.cpp
index 017e33692b..db17e50d5c 100644
--- a/src/widgets/widgets/qmainwindowlayout.cpp
+++ b/src/widgets/widgets/qmainwindowlayout.cpp
@@ -1,42 +1,6 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Copyright (C) 2015 Olivier Goffart <ogoffart@woboq.com>
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtWidgets module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2015 Olivier Goffart <ogoffart@woboq.com>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qmainwindowlayout_p.h"
@@ -60,6 +24,10 @@
#endif
#include <qapplication.h>
+#if QT_CONFIG(draganddrop)
+#include <qdrag.h>
+#endif
+#include <qmimedata.h>
#if QT_CONFIG(statusbar)
#include <qstatusbar.h>
#endif
@@ -82,8 +50,12 @@
#include <private/qlayoutengine_p.h>
#include <private/qwidgetresizehandler_p.h>
+#include <QScopedValueRollback>
+
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
extern QMainWindowLayout *qt_mainwindow_layout(const QMainWindow *window);
/******************************************************************************
@@ -100,14 +72,14 @@ static void dumpLayout(QTextStream &qout, const QDockAreaLayoutItem &item, QStri
<< "pos: " << item.pos << " size:" << item.size
<< " gap:" << (item.flags & QDockAreaLayoutItem::GapItem)
<< " keepSize:" << (item.flags & QDockAreaLayoutItem::KeepSize) << '\n';
- indent += QLatin1String(" ");
+ indent += " "_L1;
if (item.widgetItem != nullptr) {
qout << indent << "widget: "
<< item.widgetItem->widget()->metaObject()->className()
<< " \"" << item.widgetItem->widget()->windowTitle() << "\"\n";
} else if (item.subinfo != nullptr) {
qout << indent << "subinfo:\n";
- dumpLayout(qout, *item.subinfo, indent + QLatin1String(" "));
+ dumpLayout(qout, *item.subinfo, indent + " "_L1);
} else if (item.placeHolderItem != nullptr) {
QRect r = item.placeHolderItem->topLevelRect;
qout << indent << "placeHolder: "
@@ -138,11 +110,11 @@ static void dumpLayout(QTextStream &qout, const QDockAreaLayoutInfo &layout, QSt
#endif
<< '\n';
- indent += QLatin1String(" ");
+ indent += " "_L1;
- for (int i = 0; i < layout.item_list.count(); ++i) {
+ for (int i = 0; i < layout.item_list.size(); ++i) {
qout << indent << "Item: " << i << '\n';
- dumpLayout(qout, layout.item_list.at(i), indent + QLatin1String(" "));
+ dumpLayout(qout, layout.item_list.at(i), indent + " "_L1);
}
}
@@ -155,13 +127,13 @@ static void dumpLayout(QTextStream &qout, const QDockAreaLayout &layout)
<< layout.rect.height() << '\n';
qout << "TopDockArea:\n";
- dumpLayout(qout, layout.docks[QInternal::TopDock], QLatin1String(" "));
+ dumpLayout(qout, layout.docks[QInternal::TopDock], " "_L1);
qout << "LeftDockArea:\n";
- dumpLayout(qout, layout.docks[QInternal::LeftDock], QLatin1String(" "));
+ dumpLayout(qout, layout.docks[QInternal::LeftDock], " "_L1);
qout << "RightDockArea:\n";
- dumpLayout(qout, layout.docks[QInternal::RightDock], QLatin1String(" "));
+ dumpLayout(qout, layout.docks[QInternal::RightDock], " "_L1);
qout << "BottomDockArea:\n";
- dumpLayout(qout, layout.docks[QInternal::BottomDock], QLatin1String(" "));
+ dumpLayout(qout, layout.docks[QInternal::BottomDock], " "_L1);
}
QDebug operator<<(QDebug debug, const QDockAreaLayout &layout)
@@ -179,18 +151,57 @@ QDebug operator<<(QDebug debug, const QMainWindowLayout *layout)
return debug;
}
+// Use this to dump item lists of all populated main window docks.
+// Use DUMP macro inside QMainWindowLayout
+#if 0
+static void dumpItemLists(const QMainWindowLayout *layout, const char *function, const char *comment)
+{
+ for (int i = 0; i < QInternal::DockCount; ++i) {
+ const auto &list = layout->layoutState.dockAreaLayout.docks[i].item_list;
+ if (list.isEmpty())
+ continue;
+ qDebug() << function << comment << "Dock" << i << list;
+ }
+}
+#define DUMP(comment) dumpItemLists(this, __FUNCTION__, comment)
+#endif // 0
+
#endif // QT_CONFIG(dockwidget) && !defined(QT_NO_DEBUG)
-/******************************************************************************
- ** QDockWidgetGroupWindow
- */
-// QDockWidgetGroupWindow is the floating window containing several QDockWidgets floating together.
-// (QMainWindow::GroupedDragging feature)
-// QDockWidgetGroupLayout is the layout of that window and use a QDockAreaLayoutInfo to layout
-// the QDockWidgets inside it.
-// If there is only one QDockWidgets, or all QDockWidgets are tabbed together, it is equivalent
-// of a floating QDockWidget (the title of the QDockWidget is the title of the window). But if there
-// are nested QDockWidget, an additional title bar is there.
+
+/*!
+ \internal
+ QDockWidgetGroupWindow is a floating window, containing several QDockWidgets floating together.
+ This requires QMainWindow::GroupedDragging to be enabled.
+ QDockWidgets floating jointly in a QDockWidgetGroupWindow are considered to be docked.
+ Their \c isFloating property is \c false.
+ QDockWidget children of a QDockWidgetGroupWindow are either:
+ \list
+ \li tabbed (as long as Qt is compiled with the \c tabbar feature), or
+ \li arranged next to each other, equivalent to the default on a main window dock.
+ \endlist
+
+ QDockWidgetGroupWindow uses QDockWidgetGroupLayout to lay out its QDockWidget children.
+ It stores layout information in a QDockAreaLayoutInfo, including temporary spacer items
+ and rubber bands.
+
+ If its QDockWidget children are tabbed, the QDockWidgetGroupWindow shows the active QDockWidget's
+ title as its own window title.
+
+ QDockWidgetGroupWindow is designed to hold more than one QDockWidget.
+ A QDockWidgetGroupWindow with only one QDockWidget child may occur only temporarily
+ \list
+ \li in its construction phase, or
+ \li during a hover: While QDockWidget A is hovered over B, B is converted into a QDockWidgetGroupWindow.
+ \endlist
+
+ A QDockWidgetGroupWindow with only one QDockWidget child must never get focus, be dragged or dropped.
+ To enforce this restriction, QDockWidgetGrouWindow will remove itself after its second QDockWidget
+ child has been removed. It will make its last QDockWidget child a single, floating QDockWidget.
+ Eventually, the empty QDockWidgetGroupWindow will call deleteLater() on itself.
+*/
+
+
#if QT_CONFIG(dockwidget)
class QDockWidgetGroupLayout : public QLayout,
public QMainWindowLayoutSeparatorHelper<QDockWidgetGroupLayout>
@@ -294,7 +305,6 @@ bool QDockWidgetGroupWindow::event(QEvent *e)
#if QT_CONFIG(tabbar)
// Forward the close to the QDockWidget just as if its close button was pressed
if (QDockWidget *dw = activeTabbedDockWidget()) {
- e->ignore();
dw->close();
adjustFlags();
}
@@ -328,6 +338,7 @@ bool QDockWidgetGroupWindow::event(QEvent *e)
case QEvent::Resize:
updateCurrentGapRect();
emit resized();
+ break;
default:
break;
}
@@ -401,7 +412,7 @@ QDockWidget *QDockWidgetGroupWindow::activeTabbedDockWidget() const
}
}
if (!dw) {
- for (int i = 0; !dw && i < info->item_list.count(); ++i) {
+ for (int i = 0; !dw && i < info->item_list.size(); ++i) {
const QDockAreaLayoutItem &item = info->item_list.at(i);
if (item.skip())
continue;
@@ -420,22 +431,24 @@ QDockWidget *QDockWidgetGroupWindow::activeTabbedDockWidget() const
*/
void QDockWidgetGroupWindow::destroyOrHideIfEmpty()
{
- if (!layoutInfo()->isEmpty()) {
+ const QDockAreaLayoutInfo *info = layoutInfo();
+ if (!info->isEmpty()) {
show(); // It might have been hidden,
return;
}
// There might still be placeholders
- if (!layoutInfo()->item_list.isEmpty()) {
+ if (!info->item_list.isEmpty()) {
hide();
return;
}
// Make sure to reparent the possibly floating or hidden QDockWidgets to the parent
- const auto dockWidgets = findChildren<QDockWidget *>(QString(), Qt::FindDirectChildrenOnly);
- for (QDockWidget *dw : dockWidgets) {
- bool wasFloating = dw->isFloating();
- bool wasHidden = dw->isHidden();
+ const auto dockWidgetsList = dockWidgets();
+ for (QDockWidget *dw : dockWidgetsList) {
+ const bool wasFloating = dw->isFloating();
+ const bool wasHidden = dw->isHidden();
dw->setParent(parentWidget());
+ qCDebug(lcQpaDockWidgets) << "Reparented:" << dw << "to" << parentWidget() << "by" << this;
if (wasFloating) {
dw->setFloating(true);
} else {
@@ -444,20 +457,35 @@ void QDockWidgetGroupWindow::destroyOrHideIfEmpty()
qt_mainwindow_layout(static_cast<QMainWindow *>(parentWidget()));
Qt::DockWidgetArea area = ml->dockWidgetArea(this);
if (area == Qt::NoDockWidgetArea)
- area = Qt::LeftDockWidgetArea;
+ area = Qt::LeftDockWidgetArea; // FIXME: DockWidget doesn't save original docking area
static_cast<QMainWindow *>(parentWidget())->addDockWidget(area, dw);
+ qCDebug(lcQpaDockWidgets) << "Redocked to Mainwindow:" << area << dw << "by" << this;
}
if (!wasHidden)
dw->show();
}
-#if QT_CONFIG(tabbar)
- const auto tabBars = findChildren<QTabBar *>(QString(), Qt::FindDirectChildrenOnly);
- for (QTabBar *tb : tabBars)
- tb->setParent(parentWidget());
-#endif
deleteLater();
}
+/*!
+ \internal
+ \return \c true if the group window has at least one visible QDockWidget child,
+ otherwise false.
+ */
+bool QDockWidgetGroupWindow::hasVisibleDockWidgets() const
+{
+ const auto &children = findChildren<QDockWidget *>(Qt::FindChildrenRecursively);
+ for (auto child : children) {
+ // WA_WState_Visible is set on the dock widget, associated to the active tab
+ // and unset on all others.
+ // WA_WState_Hidden is set if the dock widgets have been explicitly hidden.
+ // This is the relevant information to check (equivalent to !child->isHidden()).
+ if (!child->testAttribute(Qt::WA_WState_Hidden))
+ return true;
+ }
+ return false;
+}
+
/*! \internal
Sets the flags of this window in accordance to the capabilities of the dock widgets
*/
@@ -506,7 +534,7 @@ void QDockWidgetGroupWindow::adjustFlags()
m_removedFrameSize = QSize();
}
- show(); // setWindowFlags hides the window
+ setVisible(hasVisibleDockWidgets());
}
QWidget *titleBarOf = top ? top : parentWidget();
@@ -567,8 +595,12 @@ bool QDockWidgetGroupWindow::hover(QLayoutItem *widgetItem, const QPoint &mouseP
auto newGapPos = newState.gapIndex(mousePos, nestingEnabled, tabMode);
Q_ASSERT(!newGapPos.isEmpty());
- if (newGapPos == currentGapPos)
- return false; // gap is already there
+
+ // Do not insert a new gap item, if the current position already is a gap,
+ // or if the group window contains one
+ if (newGapPos == currentGapPos || newState.hasGapItem(newGapPos))
+ return false;
+
currentGapPos = newGapPos;
newState.insertGap(currentGapPos, widgetItem);
newState.fitItems();
@@ -615,6 +647,108 @@ void QDockWidgetGroupWindow::apply()
layoutInfo()->apply(false);
}
+void QDockWidgetGroupWindow::childEvent(QChildEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::ChildRemoved:
+ if (auto *dockWidget = qobject_cast<QDockWidget *>(event->child()))
+ dockWidget->removeEventFilter(this);
+ destroyIfSingleItemLeft();
+ break;
+ case QEvent::ChildAdded:
+ if (auto *dockWidget = qobject_cast<QDockWidget *>(event->child()))
+ dockWidget->installEventFilter(this);
+ break;
+ default:
+ break;
+ }
+}
+
+bool QDockWidgetGroupWindow::eventFilter(QObject *obj, QEvent *event)
+{
+ auto *dockWidget = qobject_cast<QDockWidget *>(obj);
+ if (!dockWidget)
+ return QWidget::eventFilter(obj, event);
+
+ switch (event->type()) {
+ case QEvent::Close:
+ // We don't want closed dock widgets in a floating tab
+ // => dock it to the main dock, before closing;
+ reparent(dockWidget);
+ dockWidget->setFloating(false);
+ break;
+
+ case QEvent::Hide:
+ // if the dock widget is not an active tab, it is hidden anyway.
+ // if it is the active tab, hide the whole group.
+ if (dockWidget->isVisible())
+ hide();
+ break;
+
+ default:
+ break;
+ }
+ return QWidget::eventFilter(obj, event);
+}
+
+void QDockWidgetGroupWindow::destroyIfSingleItemLeft()
+{
+ const auto &dockWidgets = this->dockWidgets();
+
+ // Handle only the last dock
+ if (dockWidgets.count() != 1)
+ return;
+
+ auto *lastDockWidget = dockWidgets.at(0);
+
+ // If the last remaining dock widget is not in the group window's item_list,
+ // a group window is being docked on a main window docking area.
+ // => don't interfere
+ if (layoutInfo()->indexOf(lastDockWidget).isEmpty())
+ return;
+
+ auto *mainWindow = qobject_cast<QMainWindow *>(parentWidget());
+ QMainWindowLayout *mwLayout = qt_mainwindow_layout(mainWindow);
+
+ // Unplug the last remaining dock widget and hide the group window, to avoid flickering
+ mwLayout->unplug(lastDockWidget, QDockWidgetPrivate::DragScope::Widget);
+ lastDockWidget->setGeometry(geometry());
+ hide();
+
+ // Get the layout info for the main window dock, where dock widgets need to go
+ QDockAreaLayoutInfo &parentInfo = mwLayout->layoutState.dockAreaLayout.docks[layoutInfo()->dockPos];
+
+ // Re-parent last dock widget
+ reparent(lastDockWidget);
+
+ // the group window could still have placeholder items => clear everything
+ layoutInfo()->item_list.clear();
+
+ // remove the group window and the dock's item_list pointing to it.
+ parentInfo.remove(this);
+ destroyOrHideIfEmpty();
+}
+
+void QDockWidgetGroupWindow::reparent(QDockWidget *dockWidget)
+{
+ // reparent a dockWidget to the main window
+ // - remove it from the floating dock's layout info
+ // - insert it to the main dock's layout info
+ // Finally, set draggingDock to nullptr, since the drag is finished.
+ auto *mainWindow = qobject_cast<QMainWindow *>(parentWidget());
+ Q_ASSERT(mainWindow);
+ QMainWindowLayout *mwLayout = qt_mainwindow_layout(mainWindow);
+ Q_ASSERT(mwLayout);
+ QDockAreaLayoutInfo &parentInfo = mwLayout->layoutState.dockAreaLayout.docks[layoutInfo()->dockPos];
+ dockWidget->removeEventFilter(this);
+ parentInfo.add(dockWidget);
+ layoutInfo()->remove(dockWidget);
+ const bool wasFloating = dockWidget->isFloating();
+ const bool wasVisible = dockWidget->isVisible();
+ dockWidget->setParent(mainWindow);
+ dockWidget->setFloating(wasFloating);
+ dockWidget->setVisible(wasVisible);
+}
#endif
/******************************************************************************
@@ -646,7 +780,7 @@ QSize QMainWindowLayoutState::sizeHint() const
#if QT_CONFIG(dockwidget)
result = dockAreaLayout.sizeHint();
#else
- if (centralWidgetItem != 0)
+ if (centralWidgetItem)
result = centralWidgetItem->sizeHint();
#endif
@@ -664,7 +798,7 @@ QSize QMainWindowLayoutState::minimumSize() const
#if QT_CONFIG(dockwidget)
result = dockAreaLayout.minimumSize();
#else
- if (centralWidgetItem != 0)
+ if (centralWidgetItem)
result = centralWidgetItem->minimumSize();
#endif
@@ -675,6 +809,31 @@ QSize QMainWindowLayoutState::minimumSize() const
return result;
}
+/*!
+ \internal
+
+ Returns whether the layout fits into the main window.
+*/
+bool QMainWindowLayoutState::fits() const
+{
+ Q_ASSERT(mainWindow);
+
+ QSize size;
+
+#if QT_CONFIG(dockwidget)
+ size = dockAreaLayout.minimumStableSize();
+#endif
+
+#if QT_CONFIG(toolbar)
+ size.rwidth() += toolBarAreaLayout.docks[QInternal::LeftDock].rect.width();
+ size.rwidth() += toolBarAreaLayout.docks[QInternal::RightDock].rect.width();
+ size.rheight() += toolBarAreaLayout.docks[QInternal::TopDock].rect.height();
+ size.rheight() += toolBarAreaLayout.docks[QInternal::BottomDock].rect.height();
+#endif
+
+ return size.width() <= mainWindow->width() && size.height() <= mainWindow->height();
+}
+
void QMainWindowLayoutState::apply(bool animated)
{
#if QT_CONFIG(toolbar)
@@ -685,9 +844,9 @@ void QMainWindowLayoutState::apply(bool animated)
// dumpLayout(dockAreaLayout, QString());
dockAreaLayout.apply(animated);
#else
- if (centralWidgetItem != 0) {
+ if (centralWidgetItem) {
QMainWindowLayout *layout = qt_mainwindow_layout(mainWindow);
- Q_ASSERT(layout != 0);
+ Q_ASSERT(layout);
layout->widgetAnimator.animate(centralWidgetItem->widget(), centralWidgetRect, animated);
}
#endif
@@ -744,7 +903,7 @@ QLayoutItem *QMainWindowLayoutState::itemAt(int index, int *x) const
if (QLayoutItem *ret = dockAreaLayout.itemAt(x, index))
return ret;
#else
- if (centralWidgetItem != 0 && (*x)++ == index)
+ if (centralWidgetItem && (*x)++ == index)
return centralWidgetItem;
#endif
@@ -762,9 +921,9 @@ QLayoutItem *QMainWindowLayoutState::takeAt(int index, int *x)
if (QLayoutItem *ret = dockAreaLayout.takeAt(x, index))
return ret;
#else
- if (centralWidgetItem != 0 && (*x)++ == index) {
+ if (centralWidgetItem && (*x)++ == index) {
QLayoutItem *ret = centralWidgetItem;
- centralWidgetItem = 0;
+ centralWidgetItem = nullptr;
return ret;
}
#endif
@@ -807,7 +966,7 @@ bool QMainWindowLayoutState::contains(QWidget *widget) const
if (!dockAreaLayout.indexOf(widget).isEmpty())
return true;
#else
- if (centralWidgetItem != 0 && centralWidgetItem->widget() == widget)
+ if (centralWidgetItem && centralWidgetItem->widget() == widget)
return true;
#endif
@@ -856,7 +1015,7 @@ QList<int> QMainWindowLayoutState::gapIndex(QWidget *widget,
#if QT_CONFIG(toolbar)
// is it a toolbar?
- if (qobject_cast<QToolBar*>(widget) != 0) {
+ if (qobject_cast<QToolBar*>(widget) != nullptr) {
result = toolBarAreaLayout.gapIndex(pos);
if (!result.isEmpty())
result.prepend(0);
@@ -866,7 +1025,7 @@ QList<int> QMainWindowLayoutState::gapIndex(QWidget *widget,
#if QT_CONFIG(dockwidget)
// is it a dock widget?
- if (qobject_cast<QDockWidget *>(widget) != 0
+ if (qobject_cast<QDockWidget *>(widget) != nullptr
|| qobject_cast<QDockWidgetGroupWindow *>(widget)) {
bool disallowTabs = false;
#if QT_CONFIG(tabbar)
@@ -894,7 +1053,7 @@ bool QMainWindowLayoutState::insertGap(const QList<int> &path, QLayoutItem *item
#if QT_CONFIG(toolbar)
if (i == 0) {
- Q_ASSERT(qobject_cast<QToolBar*>(item->widget()) != 0);
+ Q_ASSERT(qobject_cast<QToolBar*>(item->widget()) != nullptr);
return toolBarAreaLayout.insertGap(path.mid(1), item);
}
#endif
@@ -1056,7 +1215,7 @@ void QMainWindowLayoutState::saveState(QDataStream &stream) const
dockAreaLayout.saveState(stream);
#if QT_CONFIG(tabbar)
const QList<QDockWidgetGroupWindow *> floatingTabs =
- mainWindow->findChildren<QDockWidgetGroupWindow *>(QString(), Qt::FindDirectChildrenOnly);
+ mainWindow->findChildren<QDockWidgetGroupWindow *>(Qt::FindDirectChildrenOnly);
for (QDockWidgetGroupWindow *floating : floatingTabs) {
if (floating->layoutInfo()->isEmpty())
@@ -1171,10 +1330,12 @@ bool QMainWindowLayoutState::restoreState(QDataStream &_stream,
}
QDataStream ds(copy);
+ ds.setVersion(_stream.version());
if (!checkFormat(ds))
return false;
QDataStream stream(copy);
+ stream.setVersion(_stream.version());
while (!stream.atEnd()) {
uchar marker;
@@ -1200,7 +1361,7 @@ bool QMainWindowLayoutState::restoreState(QDataStream &_stream,
if (info == nullptr) {
continue;
}
- info->item_list.append(QDockAreaLayoutItem(new QDockWidgetItem(w)));
+ info->add(w);
}
}
}
@@ -1210,8 +1371,9 @@ bool QMainWindowLayoutState::restoreState(QDataStream &_stream,
{
auto dockWidgets = allMyDockWidgets(mainWindow);
QDockWidgetGroupWindow* floatingTab = qt_mainwindow_layout(mainWindow)->createTabbedDockWindow();
- *floatingTab->layoutInfo() = QDockAreaLayoutInfo(&dockAreaLayout.sep, QInternal::LeftDock,
- Qt::Horizontal, QTabBar::RoundedSouth, mainWindow);
+ *floatingTab->layoutInfo() = QDockAreaLayoutInfo(
+ &dockAreaLayout.sep, QInternal::LeftDock, // FIXME: DockWidget doesn't save original docking area
+ Qt::Horizontal, QTabBar::RoundedSouth, mainWindow);
QRect geometry;
stream >> geometry;
QDockAreaLayoutInfo *info = floatingTab->layoutInfo();
@@ -1269,17 +1431,18 @@ bool QMainWindowLayoutState::restoreState(QDataStream &_stream,
#if QT_CONFIG(toolbar)
-static inline void validateToolBarArea(Qt::ToolBarArea &area)
+static constexpr Qt::ToolBarArea validateToolBarArea(Qt::ToolBarArea area)
{
switch (area) {
case Qt::LeftToolBarArea:
case Qt::RightToolBarArea:
case Qt::TopToolBarArea:
case Qt::BottomToolBarArea:
- break;
+ return area;
default:
- area = Qt::TopToolBarArea;
+ break;
}
+ return Qt::TopToolBarArea;
}
static QInternal::DockPosition toDockPos(Qt::ToolBarArea area)
@@ -1315,7 +1478,7 @@ static inline Qt::ToolBarArea toToolBarArea(int pos)
void QMainWindowLayout::addToolBarBreak(Qt::ToolBarArea area)
{
- validateToolBarArea(area);
+ area = validateToolBarArea(area);
layoutState.toolBarAreaLayout.addToolBarBreak(toDockPos(area));
if (savedState.isValid())
@@ -1369,7 +1532,7 @@ void QMainWindowLayout::addToolBar(Qt::ToolBarArea area,
QToolBar *toolbar,
bool)
{
- validateToolBarArea(area);
+ area = validateToolBarArea(area);
// let's add the toolbar to the layout
addChildWidget(toolbar);
QLayoutItem *item = layoutState.toolBarAreaLayout.addToolBar(toDockPos(area), toolbar);
@@ -1464,23 +1627,46 @@ static QInternal::DockPosition toDockPos(Qt::DockWidgetArea area)
return QInternal::DockCount;
}
-static Qt::DockWidgetArea toDockWidgetArea(QInternal::DockPosition pos)
+inline static Qt::DockWidgetArea toDockWidgetArea(int pos)
{
- switch (pos) {
- case QInternal::LeftDock : return Qt::LeftDockWidgetArea;
- case QInternal::RightDock : return Qt::RightDockWidgetArea;
- case QInternal::TopDock : return Qt::TopDockWidgetArea;
- case QInternal::BottomDock : return Qt::BottomDockWidgetArea;
- default:
- break;
- }
-
- return Qt::NoDockWidgetArea;
+ return QDockWidgetPrivate::toDockWidgetArea(static_cast<QInternal::DockPosition>(pos));
}
-inline static Qt::DockWidgetArea toDockWidgetArea(int pos)
+// Checks if QDockWidgetGroupWindow or QDockWidget can be plugged the area indicated by path.
+// Returns false if called with invalid widget type or if compiled without dockwidget support.
+static bool isAreaAllowed(QWidget *widget, const QList<int> &path)
{
- return toDockWidgetArea(static_cast<QInternal::DockPosition>(pos));
+ Q_ASSERT_X((path.size() > 1), "isAreaAllowed", "invalid path size");
+ const Qt::DockWidgetArea area = toDockWidgetArea(path[1]);
+
+ // Read permissions directly from a single dock widget
+ if (QDockWidget *dw = qobject_cast<QDockWidget *>(widget)) {
+ const bool allowed = dw->isAreaAllowed(area);
+ if (!allowed)
+ qCDebug(lcQpaDockWidgets) << "No permission for single DockWidget" << widget << "to dock on" << area;
+ return allowed;
+ }
+
+ // Read permissions from a DockWidgetGroupWindow depending on its DockWidget children
+ if (QDockWidgetGroupWindow *dwgw = qobject_cast<QDockWidgetGroupWindow *>(widget)) {
+ const QList<QDockWidget *> children = dwgw->findChildren<QDockWidget *>(QString(), Qt::FindDirectChildrenOnly);
+
+ if (children.size() == 1) {
+ // Group window has a single child => read its permissions
+ const bool allowed = children.at(0)->isAreaAllowed(area);
+ if (!allowed)
+ qCDebug(lcQpaDockWidgets) << "No permission for DockWidgetGroupWindow" << widget << "to dock on" << area;
+ return allowed;
+ } else {
+ // Group window has more than one or no children => dock it anywhere
+ qCDebug(lcQpaDockWidgets) << "DockWidgetGroupWindow" << widget << "has" << children.size() << "children:";
+ qCDebug(lcQpaDockWidgets) << children;
+ qCDebug(lcQpaDockWidgets) << "DockWidgetGroupWindow" << widget << "can dock at" << area << "and anywhere else.";
+ return true;
+ }
+ }
+ qCDebug(lcQpaDockWidgets) << "Docking requested for invalid widget type (coding error)." << widget << area;
+ return false;
}
void QMainWindowLayout::setCorner(Qt::Corner corner, Qt::DockWidgetArea area)
@@ -1498,6 +1684,25 @@ Qt::DockWidgetArea QMainWindowLayout::corner(Qt::Corner corner) const
return layoutState.dockAreaLayout.corners[corner];
}
+// Returns the rectangle of a dockWidgetArea
+// if max is true, the maximum possible rectangle for dropping is returned
+// the current visible rectangle otherwise
+QRect QMainWindowLayout::dockWidgetAreaRect(const Qt::DockWidgetArea area, DockWidgetAreaSize size) const
+{
+ const QInternal::DockPosition dockPosition = toDockPos(area);
+
+ // Called with invalid dock widget area
+ if (dockPosition == QInternal::DockCount) {
+ qCDebug(lcQpaDockWidgets) << "QMainWindowLayout::dockWidgetAreaRect called with" << area;
+ return QRect();
+ }
+
+ const QDockAreaLayout dl = layoutState.dockAreaLayout;
+
+ // Return maximum or visible rectangle
+ return (size == Maximum) ? dl.gapRect(dockPosition) : dl.docks[dockPosition].rect;
+}
+
void QMainWindowLayout::addDockWidget(Qt::DockWidgetArea area,
QDockWidget *dockwidget,
Qt::Orientation orientation)
@@ -1527,6 +1732,7 @@ bool QMainWindowLayout::restoreDockWidget(QDockWidget *dockwidget)
#if QT_CONFIG(tabbar)
void QMainWindowLayout::tabifyDockWidget(QDockWidget *first, QDockWidget *second)
{
+ applyRestoredState();
addChildWidget(second);
layoutState.dockAreaLayout.tabifyDockWidget(first, second);
emit second->dockLocationChanged(dockWidgetArea(first));
@@ -1546,9 +1752,9 @@ void QMainWindowLayout::setDocumentMode(bool enabled)
_documentMode = enabled;
// Update the document mode for all tab bars
- for (QTabBar *bar : qAsConst(usedTabBars))
+ for (QTabBar *bar : std::as_const(usedTabBars))
bar->setDocumentMode(_documentMode);
- for (QTabBar *bar : qAsConst(unusedTabBars))
+ for (QTabBar *bar : std::as_const(unusedTabBars))
bar->setDocumentMode(_documentMode);
}
@@ -1580,7 +1786,7 @@ void QMainWindowLayout::setTabShape(QTabWidget::TabShape tabShape)
QTabWidget::TabPosition QMainWindowLayout::tabPosition(Qt::DockWidgetArea area) const
{
- const auto dockPos = toDockPos(area);
+ const QInternal::DockPosition dockPos = toDockPos(area);
if (dockPos < QInternal::DockCount)
return tabPositions[dockPos];
qWarning("QMainWindowLayout::tabPosition called with out-of-bounds value '%d'", int(area));
@@ -1609,19 +1815,7 @@ void QMainWindowLayout::setTabPosition(Qt::DockWidgetAreas areas, QTabWidget::Ta
updateTabBarShapes();
}
-static inline QTabBar::Shape tabBarShapeFrom(QTabWidget::TabShape shape, QTabWidget::TabPosition position)
-{
- const bool rounded = (shape == QTabWidget::Rounded);
- if (position == QTabWidget::North)
- return rounded ? QTabBar::RoundedNorth : QTabBar::TriangularNorth;
- if (position == QTabWidget::South)
- return rounded ? QTabBar::RoundedSouth : QTabBar::TriangularSouth;
- if (position == QTabWidget::East)
- return rounded ? QTabBar::RoundedEast : QTabBar::TriangularEast;
- if (position == QTabWidget::West)
- return rounded ? QTabBar::RoundedWest : QTabBar::TriangularWest;
- return QTabBar::RoundedNorth;
-}
+QTabBar::Shape _q_tb_tabBarShapeFrom(QTabWidget::TabShape shape, QTabWidget::TabPosition position);
#endif // QT_CONFIG(tabwidget)
void QMainWindowLayout::updateTabBarShapes()
@@ -1647,7 +1841,7 @@ void QMainWindowLayout::updateTabBarShapes()
for (int i = 0; i < QInternal::DockCount; ++i) {
#if QT_CONFIG(tabwidget)
QTabWidget::TabPosition pos = verticalTabsEnabled ? vertical[i] : tabPositions[i];
- QTabBar::Shape shape = tabBarShapeFrom(_tabShape, pos);
+ QTabBar::Shape shape = _q_tb_tabBarShapeFrom(_tabShape, pos);
#else
QTabBar::Shape shape = verticalTabsEnabled ? vertical[i] : QTabBar::RoundedSouth;
#endif
@@ -1660,6 +1854,7 @@ void QMainWindowLayout::splitDockWidget(QDockWidget *after,
QDockWidget *dockwidget,
Qt::Orientation orientation)
{
+ applyRestoredState();
addChildWidget(dockwidget);
layoutState.dockAreaLayout.splitDockWidget(after, dockwidget, orientation);
emit dockwidget->dockLocationChanged(dockWidgetArea(after));
@@ -1684,10 +1879,15 @@ void QMainWindowLayout::keepSize(QDockWidget *w)
// Handle custom tooltip, and allow to drag tabs away.
class QMainWindowTabBar : public QTabBar
{
+ Q_OBJECT
QMainWindow *mainWindow;
QPointer<QDockWidget> draggingDock; // Currently dragging (detached) dock widget
public:
QMainWindowTabBar(QMainWindow *parent);
+ ~QMainWindowTabBar();
+ QDockWidget *dockAt(int index) const;
+ QList<QDockWidget *> dockWidgets() const;
+ bool contains(const QDockWidget *dockWidget) const;
protected:
bool event(QEvent *e) override;
void mouseReleaseEvent(QMouseEvent*) override;
@@ -1695,12 +1895,55 @@ protected:
};
+QMainWindowTabBar *QMainWindowLayout::findTabBar(const QDockWidget *dockWidget) const
+{
+ for (auto *bar : usedTabBars) {
+ Q_ASSERT(qobject_cast<QMainWindowTabBar *>(bar));
+ auto *tabBar = static_cast<QMainWindowTabBar *>(bar);
+ if (tabBar->contains(dockWidget))
+ return tabBar;
+ }
+ return nullptr;
+}
+
QMainWindowTabBar::QMainWindowTabBar(QMainWindow *parent)
: QTabBar(parent), mainWindow(parent)
{
setExpanding(false);
}
+QList<QDockWidget *> QMainWindowTabBar::dockWidgets() const
+{
+ QList<QDockWidget *> docks;
+ for (int i = 0; i < count(); ++i) {
+ if (QDockWidget *dock = dockAt(i))
+ docks << dock;
+ }
+ return docks;
+}
+
+bool QMainWindowTabBar::contains(const QDockWidget *dockWidget) const
+{
+ for (int i = 0; i < count(); ++i) {
+ if (dockAt(i) == dockWidget)
+ return true;
+ }
+ return false;
+}
+
+QDockWidget *QMainWindowTabBar::dockAt(int index) const
+{
+ QMainWindowTabBar *that = const_cast<QMainWindowTabBar *>(this);
+ QMainWindowLayout* mlayout = qt_mainwindow_layout(mainWindow);
+ QDockAreaLayoutInfo *info = mlayout->dockInfo(that);
+ if (!info)
+ return nullptr;
+ const int itemIndex = info->tabIndexToListIndex(index);
+ Q_ASSERT(itemIndex >= 0 && itemIndex < info->item_list.count());
+ const QDockAreaLayoutItem &item = info->item_list.at(itemIndex);
+ return item.widgetItem ? qobject_cast<QDockWidget *>(item.widgetItem->widget()) : nullptr;
+}
+
void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e)
{
// The QTabBar handles the moving (reordering) of tabs.
@@ -1714,13 +1957,8 @@ void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e)
offset *= 3;
QRect r = rect().adjusted(-offset, -offset, offset, offset);
if (d->dragInProgress && !r.contains(e->position().toPoint()) && d->validIndex(d->pressedIndex)) {
- QMainWindowLayout* mlayout = qt_mainwindow_layout(mainWindow);
- QDockAreaLayoutInfo *info = mlayout->dockInfo(this);
- Q_ASSERT(info);
- int idx = info->tabIndexToListIndex(d->pressedIndex);
- const QDockAreaLayoutItem &item = info->item_list.at(idx);
- if (item.widgetItem
- && (draggingDock = qobject_cast<QDockWidget *>(item.widgetItem->widget()))) {
+ draggingDock = dockAt(d->pressedIndex);
+ if (draggingDock) {
// We should drag this QDockWidget away by unpluging it.
// First cancel the QTabBar's internal move
d->moveTabFinished(d->pressedIndex);
@@ -1733,7 +1971,7 @@ void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e)
QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock));
QDockWidgetLayout *dwlayout = static_cast<QDockWidgetLayout *>(draggingDock->layout());
dockPriv->initDrag(dwlayout->titleArea().center(), true);
- dockPriv->startDrag(false);
+ dockPriv->startDrag(QDockWidgetPrivate::DragScope::Widget);
if (dockPriv->state)
dockPriv->state->ctrlDrag = e->modifiers() & Qt::ControlModifier;
}
@@ -1751,13 +1989,28 @@ void QMainWindowTabBar::mouseMoveEvent(QMouseEvent *e)
QTabBar::mouseMoveEvent(e);
}
+QMainWindowTabBar::~QMainWindowTabBar()
+{
+ if (!mainWindow || mainWindow == parentWidget())
+ return;
+
+ // tab bar is not parented to the main window
+ // => can only be a dock widget group window
+ // => remove itself from used and unused tab bar containers
+ auto *mwLayout = qt_mainwindow_layout(mainWindow);
+ if (!mwLayout)
+ return;
+ mwLayout->unusedTabBars.removeOne(this);
+ mwLayout->usedTabBars.remove(this);
+}
+
void QMainWindowTabBar::mouseReleaseEvent(QMouseEvent *e)
{
if (draggingDock && e->button() == Qt::LeftButton) {
QDockWidgetPrivate *dockPriv = static_cast<QDockWidgetPrivate *>(QObjectPrivate::get(draggingDock));
- if (dockPriv->state && dockPriv->state->dragging) {
- dockPriv->endDrag();
- }
+ if (dockPriv->state && dockPriv->state->dragging)
+ dockPriv->endDrag(QDockWidgetPrivate::EndDragMode::LocationChange);
+
draggingDock = nullptr;
}
QTabBar::mouseReleaseEvent(e);
@@ -1781,9 +2034,31 @@ bool QMainWindowTabBar::event(QEvent *e)
return true;
}
+QList<QDockWidget *> QMainWindowLayout::tabifiedDockWidgets(const QDockWidget *dockWidget) const
+{
+ const auto *bar = findTabBar(dockWidget);
+ if (!bar)
+ return {};
+
+ QList<QDockWidget *> buddies = bar->dockWidgets();
+ // Return only other dock widgets associated with dockWidget in a tab bar.
+ // If dockWidget is alone in a tab bar, return an empty list.
+ buddies.removeOne(dockWidget);
+ return buddies;
+}
+
+bool QMainWindowLayout::isDockWidgetTabbed(const QDockWidget *dockWidget) const
+{
+ // A single dock widget in a tab bar is not considered to be tabbed.
+ // This is to make sure, we don't drag an empty QDockWidgetGroupWindow around.
+ // => only consider tab bars with two or more tabs.
+ const auto *bar = findTabBar(dockWidget);
+ return bar && bar->count() > 1;
+}
+
QTabBar *QMainWindowLayout::getTabBar()
{
- if (!usedTabBars.isEmpty()) {
+ if (!usedTabBars.isEmpty() && !isInRestoreState) {
/*
If dock widgets have been removed and added while the main window was
hidden, then the layout hasn't been activated yet, and tab bars from empty
@@ -1819,7 +2094,7 @@ QWidget *QMainWindowLayout::getSeparatorWidget()
result = new QWidget(parentWidget());
result->setAttribute(Qt::WA_MouseNoMask, true);
result->setAutoFillBackground(false);
- result->setObjectName(QLatin1String("qt_qmainwindow_extended_splitter"));
+ result->setObjectName("qt_qmainwindow_extended_splitter"_L1);
}
usedSeparatorWidgets.insert(result);
return result;
@@ -1835,7 +2110,7 @@ QDockAreaLayoutInfo *QMainWindowLayout::dockInfo(QWidget *widget)
if (info)
return info;
const auto groups =
- parent()->findChildren<QDockWidgetGroupWindow*>(QString(), Qt::FindDirectChildrenOnly);
+ parent()->findChildren<QDockWidgetGroupWindow*>(Qt::FindDirectChildrenOnly);
for (QDockWidgetGroupWindow *dwgw : groups) {
info = dwgw->layoutInfo()->info(widget);
if (info)
@@ -1895,8 +2170,10 @@ void QMainWindowLayout::raise(QDockWidget *widget)
int QMainWindowLayout::count() const
{
- qWarning("QMainWindowLayout::count: ?");
- return 0; //#################################################
+ int result = 0;
+ while (itemAt(result))
+ ++result;
+ return result;
}
QLayoutItem *QMainWindowLayout::itemAt(int index) const
@@ -1953,9 +2230,37 @@ QLayoutItem *QMainWindowLayout::takeAt(int index)
return nullptr;
}
+
+/*!
+ \internal
+
+ restoredState stores what we earlier read from storage, but it couldn't
+ be applied as the mainwindow wasn't large enough (yet) to fit the state.
+ Usually, the restored state would be applied lazily in setGeometry below.
+ However, if the mainwindow's layout is modified (e.g. by a call to tabify or
+ splitDockWidgets), then we have to forget the restored state as it might contain
+ dangling pointers (QDockWidgetLayoutItem has a copy constructor that copies the
+ layout item pointer, and splitting or tabify might have to delete some of those
+ layout structures).
+
+ Functions that might result in the QMainWindowLayoutState storing dangling pointers
+ have to call this function first, so that the restoredState becomes the actual state
+ first, and is forgotten afterwards.
+*/
+void QMainWindowLayout::applyRestoredState()
+{
+ if (restoredState) {
+ layoutState = *restoredState;
+ restoredState.reset();
+ discardRestoredStateTimer.stop();
+ }
+}
+
void QMainWindowLayout::setGeometry(const QRect &_r)
{
- if (savedState.isValid())
+ // Check if the state is valid, and avoid replacing it again if it is currently used
+ // in applyState
+ if (savedState.isValid() || (restoredState && isInApplyState))
return;
QRect r = _r;
@@ -1972,11 +2277,47 @@ void QMainWindowLayout::setGeometry(const QRect &_r)
r.setBottom(sbr.top() - 1);
}
+ if (restoredState) {
+ /*
+ The main window was hidden and was going to be maximized or full-screened when
+ the state was restored. The state might have been for a larger window size than
+ the current size (in _r), and the window might still be in the process of being
+ shown and transitioning to the final size (there's no reliable way of knowing
+ this across different platforms). Try again with the restored state.
+ */
+ layoutState = *restoredState;
+ if (restoredState->fits()) {
+ restoredState.reset();
+ discardRestoredStateTimer.stop();
+ } else {
+ /*
+ Try again in the next setGeometry call, but discard the restored state
+ after 150ms without any further tries. That's a reasonably short amount of
+ time during which we can expect the windowing system to either have completed
+ showing the window, or resized the window once more (which then restarts the
+ timer in timerEvent).
+ If the windowing system is done, then the user won't have had a chance to
+ change the layout interactively AND trigger another resize.
+ */
+ discardRestoredStateTimer.start(150, this);
+ }
+ }
+
layoutState.rect = r;
+
layoutState.fitLayout();
applyState(layoutState, false);
}
+void QMainWindowLayout::timerEvent(QTimerEvent *e)
+{
+ if (e->timerId() == discardRestoredStateTimer.timerId()) {
+ discardRestoredStateTimer.stop();
+ restoredState.reset();
+ }
+ QLayout::timerEvent(e);
+}
+
void QMainWindowLayout::addItem(QLayoutItem *)
{ qWarning("QMainWindowLayout::addItem: Please use the public QMainWindow API instead"); }
@@ -2102,7 +2443,7 @@ bool QMainWindowLayout::plug(QLayoutItem *widgetItem)
previousPath = currentHoveredFloat->layoutInfo()->indexOf(widget);
// Let's remove the widget from any possible group window
const auto groups =
- parent()->findChildren<QDockWidgetGroupWindow*>(QString(), Qt::FindDirectChildrenOnly);
+ parent()->findChildren<QDockWidgetGroupWindow*>(Qt::FindDirectChildrenOnly);
for (QDockWidgetGroupWindow *dwgw : groups) {
if (dwgw == currentHoveredFloat)
continue;
@@ -2132,7 +2473,7 @@ bool QMainWindowLayout::plug(QLayoutItem *widgetItem)
#if QT_CONFIG(dockwidget)
// Let's remove the widget from any possible group window
const auto groups =
- parent()->findChildren<QDockWidgetGroupWindow*>(QString(), Qt::FindDirectChildrenOnly);
+ parent()->findChildren<QDockWidgetGroupWindow*>(Qt::FindDirectChildrenOnly);
for (QDockWidgetGroupWindow *dwgw : groups) {
QList<int> path = dwgw->layoutInfo()->indexOf(widget);
if (!path.isEmpty())
@@ -2153,7 +2494,7 @@ bool QMainWindowLayout::plug(QLayoutItem *widgetItem)
QRect globalRect = currentGapRect;
globalRect.moveTopLeft(parentWidget()->mapToGlobal(globalRect.topLeft()));
#if QT_CONFIG(dockwidget)
- if (qobject_cast<QDockWidget*>(widget) != 0) {
+ if (qobject_cast<QDockWidget*>(widget) != nullptr) {
QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(widget->layout());
if (layout->nativeWindowDeco()) {
globalRect.adjust(0, layout->titleHeight(), 0, 0);
@@ -2260,7 +2601,7 @@ void QMainWindowLayout::animationFinished(QWidget *widget)
#if QT_CONFIG(dockwidget)
#if QT_CONFIG(tabbar)
- if (qobject_cast<QDockWidget*>(widget) != 0) {
+ if (qobject_cast<QDockWidget*>(widget) != nullptr) {
// info() might return null if the widget is destroyed while
// animating but before the animationFinished signal is received.
if (QDockAreaLayoutInfo *info = dockInfo(widget))
@@ -2332,7 +2673,7 @@ QMainWindowLayout::QMainWindowLayout(QMainWindow *mainwindow, QLayout *parentLay
#endif // QT_CONFIG(dockwidget)
pluggingWidget = nullptr;
- setObjectName(mainwindow->objectName() + QLatin1String("_layout"));
+ setObjectName(mainwindow->objectName() + "_layout"_L1);
}
QMainWindowLayout::~QMainWindowLayout()
@@ -2407,7 +2748,6 @@ static bool unplugGroup(QMainWindowLayout *layout, QLayoutItem **item,
return false;
// The QDockWidget is part of a group of tab and we need to unplug them all.
-
QDockWidgetGroupWindow *floatingTabs = layout->createTabbedDockWindow();
QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
*info = std::move(*parentItem.subinfo);
@@ -2422,6 +2762,30 @@ static bool unplugGroup(QMainWindowLayout *layout, QLayoutItem **item,
}
#endif
+#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
+static QTabBar::Shape tabwidgetPositionToTabBarShape(QWidget *w)
+{
+ QTabBar::Shape result = QTabBar::RoundedSouth;
+ if (qobject_cast<QDockWidget *>(w)) {
+ switch (static_cast<QDockWidgetPrivate *>(qt_widget_private(w))->tabPosition) {
+ case QTabWidget::North:
+ result = QTabBar::RoundedNorth;
+ break;
+ case QTabWidget::South:
+ result = QTabBar::RoundedSouth;
+ break;
+ case QTabWidget::West:
+ result = QTabBar::RoundedWest;
+ break;
+ case QTabWidget::East:
+ result = QTabBar::RoundedEast;
+ break;
+ }
+ }
+ return result;
+}
+#endif // QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
+
/*! \internal
Unplug \a widget (QDockWidget or QToolBar) from it's parent container.
@@ -2432,33 +2796,35 @@ static bool unplugGroup(QMainWindowLayout *layout, QLayoutItem **item,
Returns the QLayoutItem of the dragged element.
The layout item is kept in the layout but set as a gap item.
*/
-QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group)
+QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, QDockWidgetPrivate::DragScope scope)
{
#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
auto *groupWindow = qobject_cast<const QDockWidgetGroupWindow *>(widget->parentWidget());
if (!widget->isWindow() && groupWindow) {
- if (group && groupWindow->tabLayoutInfo()) {
+ if (scope == QDockWidgetPrivate::DragScope::Group && groupWindow->tabLayoutInfo()) {
// We are just dragging a floating window as it, not need to do anything, we just have to
// look up the corresponding QWidgetItem* if it exists
if (QDockAreaLayoutInfo *info = dockInfo(widget->parentWidget())) {
QList<int> groupWindowPath = info->indexOf(widget->parentWidget());
return groupWindowPath.isEmpty() ? nullptr : info->item(groupWindowPath).widgetItem;
}
+ qCDebug(lcQpaDockWidgets) << "Drag only:" << widget << "Group:" << (scope == QDockWidgetPrivate::DragScope::Group);
return nullptr;
}
QList<int> path = groupWindow->layoutInfo()->indexOf(widget);
- QLayoutItem *item = groupWindow->layoutInfo()->item(path).widgetItem;
- if (group && path.size() > 1
- && unplugGroup(this, &item,
- groupWindow->layoutInfo()->item(path.mid(0, path.size() - 1)))) {
+ QDockAreaLayoutItem parentItem = groupWindow->layoutInfo()->item(path);
+ QLayoutItem *item = parentItem.widgetItem;
+ if (scope == QDockWidgetPrivate::DragScope::Group && path.size() > 1
+ && unplugGroup(this, &item, parentItem)) {
+ qCDebug(lcQpaDockWidgets) << "Unplugging:" << widget << "from" << item;
return item;
} else {
- // We are unplugging a dock widget from a floating window.
- QDockWidget *dw = qobject_cast<QDockWidget *>(widget);
- Q_ASSERT(dw); // cannot be a QDockWidgetGroupWindow because it's not floating.
- dw->d_func()->unplug(widget->geometry());
- groupWindow->layoutInfo()->fitItems();
- groupWindow->layoutInfo()->apply(dockOptions & QMainWindow::AnimatedDocks);
+ // We are unplugging a single dock widget from a floating window.
+ QDockWidget *dockWidget = qobject_cast<QDockWidget *>(widget);
+ Q_ASSERT(dockWidget); // cannot be a QDockWidgetGroupWindow because it's not floating.
+ dockWidget->d_func()->unplug(widget->geometry());
+
+ qCDebug(lcQpaDockWidgets) << "Unplugged from floating dock:" << widget << "from" << parentItem.widgetItem;
return item;
}
}
@@ -2478,7 +2844,7 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group)
if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget)) {
Q_ASSERT(path.constFirst() == 1);
#if QT_CONFIG(tabwidget)
- if (group && (dockOptions & QMainWindow::GroupedDragging) && path.size() > 3
+ if (scope == QDockWidgetPrivate::DragScope::Group && (dockOptions & QMainWindow::GroupedDragging) && path.size() > 3
&& unplugGroup(this, &item,
layoutState.dockAreaLayout.item(path.mid(1, path.size() - 2)))) {
path.removeLast();
@@ -2486,6 +2852,36 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group)
} else
#endif // QT_CONFIG(tabwidget)
{
+ // Dock widget is unplugged from a main window dock
+ // => height or width need to be decreased by separator size
+ switch (dockWidgetArea(dw)) {
+ case Qt::LeftDockWidgetArea:
+ case Qt::RightDockWidgetArea:
+ r.setHeight(r.height() - sep);
+ break;
+ case Qt::TopDockWidgetArea:
+ case Qt::BottomDockWidgetArea:
+ r.setWidth(r.width() - sep);
+ break;
+ case Qt::NoDockWidgetArea:
+ case Qt::DockWidgetArea_Mask:
+ break;
+ }
+
+ // Depending on the title bar layout (vertical / horizontal),
+ // width and height have to provide minimum space for window handles
+ // and mouse dragging.
+ // Assuming horizontal title bar, if the dock widget does not have a layout.
+ const auto *layout = qobject_cast<QDockWidgetLayout *>(dw->layout());
+ const bool verticalTitleBar = layout ? layout->verticalTitleBar : false;
+ const int tbHeight = QApplication::style()
+ ? QApplication::style()->pixelMetric(QStyle::PixelMetric::PM_TitleBarHeight, nullptr, dw)
+ : 20;
+ const int minHeight = verticalTitleBar ? 2 * tbHeight : tbHeight;
+ const int minWidth = verticalTitleBar ? tbHeight : 2 * tbHeight;
+ r.setSize(r.size().expandedTo(QSize(minWidth, minHeight)));
+ qCDebug(lcQpaDockWidgets) << dw << "will be unplugged with size" << r.size();
+
dw->d_func()->unplug(r);
}
}
@@ -2497,7 +2893,7 @@ QLayoutItem *QMainWindowLayout::unplug(QWidget *widget, bool group)
#endif
#if !QT_CONFIG(dockwidget) || !QT_CONFIG(tabbar)
- Q_UNUSED(group);
+ Q_UNUSED(scope);
#endif
layoutState.unplug(path ,&savedState);
@@ -2527,58 +2923,45 @@ void QMainWindowLayout::updateGapIndicator()
if (!gapIndicator) {
gapIndicator = new QRubberBand(QRubberBand::Rectangle, expectedParent);
// For accessibility to identify this special widget.
- gapIndicator->setObjectName(QLatin1String("qt_rubberband"));
+ gapIndicator->setObjectName("qt_rubberband"_L1);
} else if (gapIndicator->parent() != expectedParent) {
gapIndicator->setParent(expectedParent);
}
+ // Prevent re-entry in case of size change
+ const bool sigBlockState = gapIndicator->signalsBlocked();
+ auto resetSignals = qScopeGuard([this, sigBlockState](){ gapIndicator->blockSignals(sigBlockState); });
+ gapIndicator->blockSignals(true);
+
#if QT_CONFIG(dockwidget)
if (currentHoveredFloat)
gapIndicator->setGeometry(currentHoveredFloat->currentGapRect);
else
#endif
gapIndicator->setGeometry(currentGapRect);
+
gapIndicator->show();
gapIndicator->raise();
+
+ // Reset signal state
+
} else if (gapIndicator) {
gapIndicator->hide();
}
-#endif // QT_CONFIG(rubberband)
-}
-#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
-static QTabBar::Shape tabwidgetPositionToTabBarShape(QWidget *w)
-{
- QTabBar::Shape result = QTabBar::RoundedSouth;
- if (qobject_cast<QDockWidget *>(w)) {
- switch (static_cast<QDockWidgetPrivate *>(qt_widget_private(w))->tabPosition) {
- case QTabWidget::North:
- result = QTabBar::RoundedNorth;
- break;
- case QTabWidget::South:
- result = QTabBar::RoundedSouth;
- break;
- case QTabWidget::West:
- result = QTabBar::RoundedWest;
- break;
- case QTabWidget::East:
- result = QTabBar::RoundedEast;
- break;
- }
- }
- return result;
+#endif // QT_CONFIG(rubberband)
}
-#endif // QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
-void QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mousePos)
-{
- if (!parentWidget()->isVisible() || parentWidget()->isMinimized()
- || pluggingWidget != nullptr || widgetItem == nullptr)
- return;
+void QMainWindowLayout::hover(QLayoutItem *hoverTarget,
+ const QPoint &mousePos) {
+ if (!parentWidget()->isVisible() || parentWidget()->isMinimized() ||
+ pluggingWidget != nullptr || hoverTarget == nullptr)
+ return;
- QWidget *widget = widgetItem->widget();
+ QWidget *widget = hoverTarget->widget();
#if QT_CONFIG(dockwidget)
+ widget->raise();
if ((dockOptions & QMainWindow::GroupedDragging) && (qobject_cast<QDockWidget*>(widget)
|| qobject_cast<QDockWidgetGroupWindow *>(widget))) {
@@ -2589,12 +2972,20 @@ void QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mousePos)
QWidget *w = qobject_cast<QWidget*>(c);
if (!w)
continue;
+
+ // Handle only dock widgets and group windows
if (!qobject_cast<QDockWidget*>(w) && !qobject_cast<QDockWidgetGroupWindow *>(w))
continue;
- if (w != widget && w->isTopLevel() && w->isVisible() && !w->isMinimized())
+
+ // Check permission to dock on another dock widget or floating dock
+ // FIXME in Qt 7
+
+ if (w != widget && w->isWindow() && w->isVisible() && !w->isMinimized())
candidates << w;
+
if (QDockWidgetGroupWindow *group = qobject_cast<QDockWidgetGroupWindow *>(w)) {
- // Sometimes, there are floating QDockWidget that have a QDockWidgetGroupWindow as a parent.
+ // floating QDockWidgets have a QDockWidgetGroupWindow as a parent,
+ // if they have been hovered over
const auto groupChildren = group->children();
for (QObject *c : groupChildren) {
if (QDockWidget *dw = qobject_cast<QDockWidget*>(c)) {
@@ -2604,6 +2995,7 @@ void QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mousePos)
}
}
}
+
for (QWidget *w : candidates) {
const QScreen *screen1 = qt_widget_private(widget)->associatedScreen();
const QScreen *screen2 = qt_widget_private(w)->associatedScreen();
@@ -2614,36 +3006,63 @@ void QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mousePos)
#if QT_CONFIG(tabwidget)
if (auto dropTo = qobject_cast<QDockWidget *>(w)) {
- // dropping to a normal widget, we mutate it in a QDockWidgetGroupWindow with two
- // tabs
- QDockWidgetGroupWindow *floatingTabs = createTabbedDockWindow(); // FIXME
- floatingTabs->setGeometry(dropTo->geometry());
- QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
- const QTabBar::Shape shape = tabwidgetPositionToTabBarShape(dropTo);
- *info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, QInternal::LeftDock,
- Qt::Horizontal, shape,
- static_cast<QMainWindow *>(parentWidget()));
- info->tabbed = true;
- QLayout *parentLayout = dropTo->parentWidget()->layout();
- info->item_list.append(
- QDockAreaLayoutItem(parentLayout->takeAt(parentLayout->indexOf(dropTo))));
-
- dropTo->setParent(floatingTabs);
+
+ // w is the drop target's widget
+ w = dropTo->widget();
+
+ // Create a floating tab, unless already existing
+ if (!qobject_cast<QDockWidgetGroupWindow *>(w)) {
+ QDockWidgetGroupWindow *floatingTabs = createTabbedDockWindow();
+ floatingTabs->setGeometry(dropTo->geometry());
+ QDockAreaLayoutInfo *info = floatingTabs->layoutInfo();
+ const QTabBar::Shape shape = tabwidgetPositionToTabBarShape(dropTo);
+
+ // dropTo and widget may be in a state where they transition
+ // from being a group window child to a single floating dock widget.
+ // In that case, their path to a main window dock may not have been
+ // updated yet.
+ // => ask both and fall back to dock 1 (right dock)
+ QInternal::DockPosition dockPosition = toDockPos(dockWidgetArea(dropTo));
+ if (dockPosition == QInternal::DockPosition::DockCount)
+ dockPosition = toDockPos(dockWidgetArea(widget));
+ if (dockPosition == QInternal::DockPosition::DockCount)
+ dockPosition = QInternal::DockPosition::RightDock;
+
+ *info = QDockAreaLayoutInfo(&layoutState.dockAreaLayout.sep, dockPosition,
+ Qt::Horizontal, shape,
+ static_cast<QMainWindow *>(parentWidget()));
+ info->tabBar = getTabBar();
+ info->tabbed = true;
+ info->add(dropTo);
+ QDockAreaLayoutInfo &parentInfo = layoutState.dockAreaLayout.docks[dockPosition];
+ parentInfo.add(floatingTabs);
+ dropTo->setParent(floatingTabs);
+ qCDebug(lcQpaDockWidgets) << "Wrapping" << widget << "into floating tabs" << floatingTabs;
+ w = floatingTabs;
+ }
+
+ // Show the drop target and raise widget to foreground
dropTo->show();
- dropTo->d_func()->plug(QRect());
- w = floatingTabs;
- widget->raise(); // raise, as our newly created drop target is now on top
+ qCDebug(lcQpaDockWidgets) << "Showing" << dropTo;
+ widget->raise();
+ qCDebug(lcQpaDockWidgets) << "Raising" << widget;
}
#endif
- Q_ASSERT(qobject_cast<QDockWidgetGroupWindow *>(w));
- auto group = static_cast<QDockWidgetGroupWindow *>(w);
- if (group->hover(widgetItem, group->mapFromGlobal(mousePos))) {
- setCurrentHoveredFloat(group);
+ auto *groupWindow = qobject_cast<QDockWidgetGroupWindow *>(w);
+ Q_ASSERT(groupWindow);
+ if (groupWindow->hover(hoverTarget, groupWindow->mapFromGlobal(mousePos))) {
+ setCurrentHoveredFloat(groupWindow);
applyState(layoutState); // update the tabbars
}
return;
}
}
+
+ // If a temporary group window has been created during a hover,
+ // remove it, if it has only one dockwidget child
+ if (currentHoveredFloat)
+ currentHoveredFloat->destroyIfSingleItemLeft();
+
setCurrentHoveredFloat(nullptr);
layoutState.dockAreaLayout.fallbackToSizeHints = false;
#endif // QT_CONFIG(dockwidget)
@@ -2659,11 +3078,7 @@ void QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mousePos)
bool allowed = false;
#if QT_CONFIG(dockwidget)
- if (QDockWidget *dw = qobject_cast<QDockWidget*>(widget))
- allowed = dw->isAreaAllowed(toDockWidgetArea(path.at(1)));
-
- if (qobject_cast<QDockWidgetGroupWindow *>(widget))
- allowed = true;
+ allowed = isAreaAllowed(widget, path);
#endif
#if QT_CONFIG(toolbar)
if (QToolBar *tb = qobject_cast<QToolBar*>(widget))
@@ -2679,16 +3094,16 @@ void QMainWindowLayout::hover(QLayoutItem *widgetItem, const QPoint &mousePos)
currentGapPos = path;
if (path.isEmpty()) {
- fixToolBarOrientation(widgetItem, 2); // 2 = top dock, ie. horizontal
+ fixToolBarOrientation(hoverTarget, 2); // 2 = top dock, ie. horizontal
restore(true);
return;
}
- fixToolBarOrientation(widgetItem, currentGapPos.at(1));
+ fixToolBarOrientation(hoverTarget, currentGapPos.at(1));
QMainWindowLayoutState newState = savedState;
- if (!newState.insertGap(path, widgetItem)) {
+ if (!newState.insertGap(path, hoverTarget)) {
restore(true); // not enough space
return;
}
@@ -2725,10 +3140,16 @@ QDockWidgetGroupWindow *QMainWindowLayout::createTabbedDockWindow()
void QMainWindowLayout::applyState(QMainWindowLayoutState &newState, bool animate)
{
+ // applying the state can lead to showing separator widgets, which would lead to a re-layout
+ // (even though the separator widgets are not really part of the layout)
+ // break the loop
+ if (isInApplyState)
+ return;
+ isInApplyState = true;
#if QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
QSet<QTabBar*> used = newState.dockAreaLayout.usedTabBars();
const auto groups =
- parent()->findChildren<QDockWidgetGroupWindow*>(QString(), Qt::FindDirectChildrenOnly);
+ parent()->findChildren<QDockWidgetGroupWindow*>(Qt::FindDirectChildrenOnly);
for (QDockWidgetGroupWindow *dwgw : groups)
used += dwgw->layoutInfo()->usedTabBars();
@@ -2747,6 +3168,7 @@ void QMainWindowLayout::applyState(QMainWindowLayoutState &newState, bool animat
usedSeparatorWidgets = usedSeps;
for (QWidget *sepWidget : retiredSeps) {
unusedSeparatorWidgets.append(sepWidget);
+ sepWidget->hide();
}
}
@@ -2755,6 +3177,7 @@ void QMainWindowLayout::applyState(QMainWindowLayoutState &newState, bool animat
#endif // QT_CONFIG(dockwidget) && QT_CONFIG(tabwidget)
newState.apply(dockOptions & QMainWindow::AnimatedDocks && animate);
+ isInApplyState = false;
}
void QMainWindowLayout::saveState(QDataStream &stream) const
@@ -2764,6 +3187,7 @@ void QMainWindowLayout::saveState(QDataStream &stream) const
bool QMainWindowLayout::restoreState(QDataStream &stream)
{
+ QScopedValueRollback<bool> guard(isInRestoreState, true);
savedState = layoutState;
layoutState.clear();
layoutState.rect = savedState.rect;
@@ -2779,6 +3203,18 @@ bool QMainWindowLayout::restoreState(QDataStream &stream)
if (parentWidget()->isVisible()) {
layoutState.fitLayout();
applyState(layoutState, false);
+ } else {
+ /*
+ The state might not fit into the size of the widget as it gets shown, but
+ if the window is expected to be maximized or full screened, then we might
+ get several resizes as part of that transition, at the end of which the
+ state might fit. So keep the restored state around for now and try again
+ later in setGeometry.
+ */
+ if ((parentWidget()->windowState() & (Qt::WindowFullScreen | Qt::WindowMaximized))
+ && !layoutState.fits()) {
+ restoredState.reset(new QMainWindowLayoutState(layoutState));
+ }
}
savedState.deleteAllLayoutItems();
@@ -2787,7 +3223,7 @@ bool QMainWindowLayout::restoreState(QDataStream &stream)
#if QT_CONFIG(dockwidget)
if (parentWidget()->isVisible()) {
#if QT_CONFIG(tabbar)
- for (QTabBar *tab_bar : qAsConst(usedTabBars))
+ for (QTabBar *tab_bar : std::as_const(usedTabBars))
tab_bar->show();
#endif
@@ -2797,6 +3233,42 @@ bool QMainWindowLayout::restoreState(QDataStream &stream)
return true;
}
+#if QT_CONFIG(draganddrop)
+bool QMainWindowLayout::needsPlatformDrag()
+{
+ static const bool wayland =
+ QGuiApplication::platformName().startsWith("wayland"_L1, Qt::CaseInsensitive);
+ return wayland;
+}
+
+Qt::DropAction QMainWindowLayout::performPlatformWidgetDrag(QLayoutItem *widgetItem,
+ const QPoint &pressPosition)
+{
+ draggingWidget = widgetItem;
+ QWidget *widget = widgetItem->widget();
+ auto drag = QDrag(widget);
+ auto mimeData = new QMimeData();
+ auto window = widgetItem->widget()->windowHandle();
+
+ auto serialize = [](const auto &object) {
+ QByteArray data;
+ QDataStream dataStream(&data, QIODevice::WriteOnly);
+ dataStream << object;
+ return data;
+ };
+ mimeData->setData("application/x-qt-mainwindowdrag-window"_L1,
+ serialize(reinterpret_cast<qintptr>(window)));
+ mimeData->setData("application/x-qt-mainwindowdrag-position"_L1, serialize(pressPosition));
+ drag.setMimeData(mimeData);
+
+ auto result = drag.exec();
+
+ draggingWidget = nullptr;
+ return result;
+}
+#endif
+
QT_END_NAMESPACE
+#include "qmainwindowlayout.moc"
#include "moc_qmainwindowlayout_p.cpp"