summaryrefslogtreecommitdiffstats
path: root/src/widgets/widgets/qdockwidget.cpp
diff options
context:
space:
mode:
authorAxel Spoerl <axel.spoerl@qt.io>2023-11-15 12:40:55 +0100
committerAxel Spoerl <axel.spoerl@qt.io>2023-11-18 20:48:44 +0100
commite6d85cf28bc4f750b69c33b72c006b067ae1190f (patch)
tree591b15def951b8d0f78844be1cd5bd3ea76b2f28 /src/widgets/widgets/qdockwidget.cpp
parent0b10b7476cf9d41086063ec49555425c6871041c (diff)
QDockWidget: Fix group unplugging
A floating dock widget could either be a single QDockWidget object, or a QDockWidgetGroupWindow with a single QDockWidget child. The former could be dropped on the latter. Dropping the latter on the former caused a crash. The existence of QDockWidgetGroupWindows with a single dock widget child was accepted to be by design. Previous fixes, such as 9ff40b59da58160dc26c54204a615a2456e07405, attempted to wrap all single floating dock widgets in QDockWidgetGroupWindows. These attempts fell short, because of the manifold programmatic and manual options to create a floating dock widget: - drag a single dock widget out of a main window dock area - drag a dock widget out of a tab bar on the main window - drag a dock widget out of a floating tab - call `QDockWidget::setFloating(true)` in any situation - create a new QDockWidget, that floats from the beginning Whenever a QDockWidgetGroupWindow with a single QDockWidget child was hovered and/or dropped on a QDockWidget without a group window, crashes or screen artifacts were observed. Previous fixes made them occur less often. QDockWidgetGroupWindow is not designed to hold a single QDockWidget child. Such a state is inconsistent and may only exist, while a QDockWidgetGroupWindow is constructed. The reason why such invalid QDockWidgetGroupWindows started to exist, is a bool trap: QDockWidgetPrivate::mouseMoveEvent() starts a drag operation, when a dock widget is moved by mouse. It called startDrag() with no argument, which defaulted to startDrag(true) and caused a group drag. This assumption is *correct*, when a tabbed group of dock widgets is dragged out of the main dock as a whole, to become floating tabs. *wrong*, when a single dock widget is dragged out of a docked group, to become a single floating dock widget. In the second case, the dock widget was wrapped in a new, floating, invisible QDockWidgetGroupWindow. Looking like a single, floating dock widget, the group window caused a crash, when attempted to be dropped on another dock widget. This patch eliminates all cases, where a QDockWidgetGroupWindow with a single QDockWidget is created: (1) Implement QDockWidgetPrivate::isTabbed(). This enables mouseMoveEvent to determine, whether the move relates to a group of tabbed dock widgets, or to a single dock widget. startDrag() can therefore be called with the right argument. It will no longer create a QDockWidgetGroupWindow with a single QDockWidget child. (2) Change QMainWindowTabBar::mouseReleaseEvent When a dock widget was dragged out of a tab bar and became a single, floating dock widget, it was still parented to the group window. That is wrong, because it has no more relationship with that group window. => Reparent it to the main window, just like any other single floating dock widget. That enables QDockWidgetGroupWindow to detect, that the 2nd last child has gone and no more group window is needed (see next point). (3) React to reparenting, closing and deleting If the second last dock widget in a floating tab gets closed (manually or programmatically), reparented or deleted, also unplug the last one and remove the group window. (4) Amend 9ff40b59da58160dc26c54204a615a2456e07405 Remove the code path where a QDockWidgetGroupWindow with a single QDockWidget child was created 'just in case', to make it compatible others, created by (1), (2) or (3). (5) Change QMainWindowLayout::hover() When the hover ends without a successful drop and a temporary group window with a single dock widget child has been created, remove the group window. The patch fixes smaller inconsistencies, which have not become visible due to assertions and crashes earlier in the chain. The patch finally extends tst_QDockWidget, to cover all 4 cases. - Creation of floating tabs The creation of floating tabs is extracted from floatingTabs() to the helper function createFloatingTabs(). In addition to creating floating tabs, the helper verifies that dragging a dock widget out of the main window doesn't accidently wrap it in a group window. This covers case (1). - tst_QDockWidget::floatingTabs() The test function verifies now, that both test dock widgets have the same path before plugging them together and after unplugging them from the floating tab. This covers case(4). - tst_QDockwidget::deleteFloatingTabWithSingleDockWidget() This test function is added, to cover cases (2) and (3). - tst_QDockWidget::hoverWithoutDrop() This test function hovers two floating dock widgets hover each other, and returns the moved dock widget to its origin before releasing the mouse. This covers case(5). This fixes a lot of long standing bugs, making the author of this patch modestly happy :-) Fixes: QTBUG-118223 Fixes: QTBUG-99136 Fixes: QTBUG-118578 Fixes: QTBUG-118579 Fixes: QTBUG-56799 Fixes: QTBUG-35736 Fixes: QTBUG-63448 Fixes: QTBUG-88329 Fixes: QTBUG-88157 Fixes: QTBUG-94097 Fixes: QTBUG-44540 Fixes: QTBUG-53808 Fixes: QTBUG-72915 Fixes: QTBUG-53438 Found-by: Keith Kyzivat <keith.kyzivat@qt.io> Found-by: Frederic Lefebvre <frederic.lefebvre@qt.io> Pick-to: 6.6 6.5 Change-Id: I51b5f9e40cb2dbe55fb14d769541067730538463 Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Diffstat (limited to 'src/widgets/widgets/qdockwidget.cpp')
-rw-r--r--src/widgets/widgets/qdockwidget.cpp17
1 files changed, 16 insertions, 1 deletions
diff --git a/src/widgets/widgets/qdockwidget.cpp b/src/widgets/widgets/qdockwidget.cpp
index ff4fda6bad..f76975859f 100644
--- a/src/widgets/widgets/qdockwidget.cpp
+++ b/src/widgets/widgets/qdockwidget.cpp
@@ -847,6 +847,11 @@ void QDockWidgetPrivate::endDrag(EndDragMode mode)
tabPosition = mwLayout->tabPosition(toDockWidgetArea(dwgw->layoutInfo()->dockPos));
}
#endif
+ // Reparent, if the drag was out of a dock widget group window
+ if (mode == EndDragMode::LocationChange) {
+ if (auto *groupWindow = qobject_cast<QDockWidgetGroupWindow *>(q->parentWidget()))
+ groupWindow->reparent(q);
+ }
}
q->activateWindow();
} else {
@@ -948,6 +953,15 @@ bool QDockWidgetPrivate::mouseDoubleClickEvent(QMouseEvent *event)
return false;
}
+bool QDockWidgetPrivate::isTabbed() const
+{
+ Q_Q(const QDockWidget);
+ QDockWidget *that = const_cast<QDockWidget *>(q);
+ auto *mwLayout = qt_mainwindow_layout_from_dock(that);
+ Q_ASSERT(mwLayout);
+ return mwLayout->isDockWidgetTabbed(q);
+}
+
bool QDockWidgetPrivate::mouseMoveEvent(QMouseEvent *event)
{
bool ret = false;
@@ -978,7 +992,8 @@ bool QDockWidgetPrivate::mouseMoveEvent(QMouseEvent *event)
} else
#endif
{
- startDrag(DragScope::Group);
+ const DragScope scope = isTabbed() ? DragScope::Group : DragScope::Widget;
+ startDrag(scope);
q->grabMouse();
ret = true;
}