diff options
author | Doris Verria <[email protected]> | 2024-03-26 15:57:56 +0100 |
---|---|---|
committer | Doris Verria <[email protected]> | 2024-04-22 16:01:01 +0200 |
commit | f20be43b827ffa5c9361bc128fd930841435bdf6 (patch) | |
tree | 316107f38fb6b46686dadcf0da0869ba6171b70c | |
parent | d06cff08598129c85952f4d45a382d300cb4ac65 (diff) |
Set focus to the window container when contained window gains focus
As it is, when a QWindowContainer's embedded window gains focus, the
container doesn't report having focus and QApplication::focusWidget()
will be nullptr. This is because when the embedded window gets focus,
the container will clearFocus() on the old focus widget. To be able
to set focus to the next focus widget (eg: as a result of a key tab
event sent to the container's parent window), the container checks
if its embedded window is already focused, and if so, forwards focus
to its nextInFocusChain(). That is why it keeps track of the (old)
focusWindow.
The problem with the current behavior is that if we want to make focus
navigation via key tabbing work and return focus *from within the
window* back to the normal widget focus chain, the encapsulating widget
needs to remember its focusWidget(), in this case the window container,
in order to be able to set focus to the next/PrevInFocusChain().
That is why we now set the focus to the window container whenever its
contained window gains focus. This means that
QApplication::focusWidget() will be the window container if the contained
window has focus. In this way, we don't have to call clearFocus() on the
old focus widget, or keep track of focus windows, because calling
setFocus() on the container will handle that.
It is worth noting and probably documenting the following caveats:
- even though the window container will be the
qApp's focusWidget(), it won't directly handle keyboard events, but the
contained window will
- we won't be able to respect the window container's focusPolicy in this
case, since the contained window will be activated from interactions
inside it, no matter the container's focusPolicy
[ChangeLog][QtWidgets][QWindowContainer] The window container will
become focused if the contained window becomes focused. This
implies that the QApplication::focusWidget() will be the window
container if the contained window is the focus window.
Task-number: QTBUG-121789
Change-Id: I1050afc59780f7189a0d8e8c95bff27f96f38dbc
Reviewed-by: Axel Spoerl <[email protected]>
-rw-r--r-- | src/widgets/kernel/qwidget.cpp | 4 | ||||
-rw-r--r-- | src/widgets/kernel/qwindowcontainer.cpp | 40 | ||||
-rw-r--r-- | src/widgets/kernel/qwindowcontainer_p.h | 3 | ||||
-rw-r--r-- | tests/auto/widgets/kernel/qwindowcontainer/tst_qwindowcontainer.cpp | 62 |
4 files changed, 74 insertions, 35 deletions
diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index 2cc40fc7ad6..b302b32b88f 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -6616,7 +6616,9 @@ void QWidgetPrivate::setFocus_sys() { Q_Q(QWidget); // Embedded native widget may have taken the focus; get it back to toplevel - // if that is the case (QTBUG-25852) + // if that is the case (QTBUG-25852), unless widget is a window container. + if (extra && extra->hasWindowContainer) + return; // Do not activate in case the popup menu opens another application (QTBUG-70810) // unless the application is embedded (QTBUG-71991). if (QWindow *nativeWindow = q->testAttribute(Qt::WA_WState_Created) ? q->window()->windowHandle() : nullptr) { diff --git a/src/widgets/kernel/qwindowcontainer.cpp b/src/widgets/kernel/qwindowcontainer.cpp index c15ec54f350..2c374ac408a 100644 --- a/src/widgets/kernel/qwindowcontainer.cpp +++ b/src/widgets/kernel/qwindowcontainer.cpp @@ -28,7 +28,6 @@ public: QWindowContainerPrivate() : window(nullptr) - , oldFocusWindow(nullptr) , usesNativeWidgets(false) { } @@ -103,7 +102,6 @@ public: } QPointer<QWindow> window; - QWindow *oldFocusWindow; QWindow fakeParent; uint usesNativeWidgets : 1; @@ -207,6 +205,7 @@ QWindowContainer::QWindowContainer(QWindow *embeddedWindow, QWidget *parent, Qt: } d->window = embeddedWindow; + d->window->installEventFilter(this); QString windowName = d->window->objectName(); if (windowName.isEmpty()) @@ -219,9 +218,6 @@ QWindowContainer::QWindowContainer(QWindow *embeddedWindow, QWidget *parent, Qt: setAcceptDrops(true); - connect(qGuiApp, &QGuiApplication::focusWindowChanged, - this, &QWindowContainer::focusWindowChanged); - connect(containedWindow(), &QWindow::minimumHeightChanged, this, &QWindowContainer::updateGeometry); connect(containedWindow(), &QWindow::minimumWidthChanged, this, &QWindowContainer::updateGeometry); } @@ -244,30 +240,12 @@ QWindowContainer::~QWindowContainer() // QEvent::PlatformSurface delivery relies on virtuals. Getting // SurfaceAboutToBeDestroyed can be essential for OpenGL, Vulkan, etc. // QWindow subclasses in particular. Keep these working. - if (d->window) + if (d->window) { + d->window->removeEventFilter(this); d->window->destroy(); + } delete d->window; - - disconnect(qGuiApp, &QGuiApplication::focusWindowChanged, - this, &QWindowContainer::focusWindowChanged); -} - - - -/*! - \internal - */ - -void QWindowContainer::focusWindowChanged(QWindow *focusWindow) -{ - Q_D(QWindowContainer); - d->oldFocusWindow = focusWindow; - if (focusWindow == d->window) { - QWidget *widget = QApplication::focusWidget(); - if (widget) - widget->clearFocus(); - } } /*! @@ -284,8 +262,12 @@ bool QWindowContainer::eventFilter(QObject *o, QEvent *e) QChildEvent *ce = static_cast<QChildEvent *>(e); if (ce->child() == d->window) { o->removeEventFilter(this); + d->window->removeEventFilter(this); d->window = nullptr; } + } else if (e->type() == QEvent::FocusIn) { + if (o == d->window) + setFocus(Qt::ActiveWindowFocusReason); } return false; } @@ -335,12 +317,8 @@ bool QWindowContainer::event(QEvent *e) break; case QEvent::FocusIn: if (d->window->parent()) { - if (d->oldFocusWindow != d->window) { + if (QGuiApplication::focusWindow() != d->window) d->window->requestActivate(); - } else { - QWidget *next = nextInFocusChain(); - next->setFocus(); - } } break; #if QT_CONFIG(draganddrop) diff --git a/src/widgets/kernel/qwindowcontainer_p.h b/src/widgets/kernel/qwindowcontainer_p.h index a303f254241..0cbcc5321d4 100644 --- a/src/widgets/kernel/qwindowcontainer_p.h +++ b/src/widgets/kernel/qwindowcontainer_p.h @@ -42,9 +42,6 @@ public: protected: bool event(QEvent *ev) override; bool eventFilter(QObject *, QEvent *ev) override; - -private slots: - void focusWindowChanged(QWindow *focusWindow); }; QT_END_NAMESPACE diff --git a/tests/auto/widgets/kernel/qwindowcontainer/tst_qwindowcontainer.cpp b/tests/auto/widgets/kernel/qwindowcontainer/tst_qwindowcontainer.cpp index e620c5e79a3..52aaf094b47 100644 --- a/tests/auto/widgets/kernel/qwindowcontainer/tst_qwindowcontainer.cpp +++ b/tests/auto/widgets/kernel/qwindowcontainer/tst_qwindowcontainer.cpp @@ -7,6 +7,7 @@ #include <qapplication.h> #include <qwindow.h> #include <qwidget.h> +#include <qlineedit.h> #include <qdockwidget.h> #include <qmainwindow.h> @@ -60,6 +61,7 @@ private slots: void testNativeContainerParent(); void testPlatformSurfaceEvent(); void embedWidgetWindow(); + void testFocus(); void cleanup(); private: @@ -468,6 +470,66 @@ void tst_QWindowContainer::embedWidgetWindow() } +void tst_QWindowContainer::testFocus() +{ + QWidget root; + root.setGeometry(m_availableGeometry); + + QLineEdit *lineEdit = new QLineEdit(&root); + lineEdit->setGeometry(0, 0, m_availableGeometry.width() * 0.1, 17); + lineEdit->setFocusPolicy(Qt::FocusPolicy::StrongFocus); + + QWindow *embedded = new QWindow(); + QWidget *container = QWidget::createWindowContainer(embedded, &root); + container->setGeometry(0, lineEdit->height() + 10, m_availableGeometry.width() * 0.2, m_availableGeometry.height() - (lineEdit->height() + 10)); + container->setFocusPolicy(Qt::StrongFocus); + + root.show(); + QVERIFY(QTest::qWaitForWindowExposed(&root)); + lineEdit->setFocus(); + QTRY_VERIFY(lineEdit->hasFocus()); + QCOMPARE(QGuiApplication::focusWindow(), root.windowHandle()); + QCOMPARE(QApplication::focusWidget(), lineEdit); + + // embedded window gets focused because of mouse click + QPoint embeddedCenter = container->rect().center(); + QTest::mousePress(root.windowHandle(), Qt::LeftButton, {}, embeddedCenter); + QVERIFY(QTest::qWaitForWindowFocused(embedded)); + QVERIFY(container->hasFocus()); + QCOMPARE(QGuiApplication::focusWindow(), embedded); + QCOMPARE(QApplication::focusWidget(), container); + QVERIFY(!lineEdit->hasFocus()); + + QTest::mouseClick(lineEdit, Qt::LeftButton, {}); + QVERIFY(QTest::qWaitForWindowFocused(root.windowHandle())); + QCOMPARE(QGuiApplication::focusWindow(), root.windowHandle()); + QCOMPARE(QApplication::focusWidget(), lineEdit); + QVERIFY(lineEdit->hasFocus()); + + // embedded window gets focused because of Tab key event + QTest::keyClick(root.windowHandle(), Qt::Key_Tab); + QVERIFY(QTest::qWaitForWindowFocused(embedded)); + QVERIFY(container->hasFocus()); + QCOMPARE(QGuiApplication::focusWindow(), embedded); + QCOMPARE(QApplication::focusWidget(), container); + QVERIFY(!lineEdit->hasFocus()); + // A key tab event sent to the root window should cause + // the nextInFocusChain of the window container to get focused + QTest::keyClick(root.windowHandle(), Qt::Key_Tab); + QVERIFY(QTest::qWaitForWindowFocused(root.windowHandle())); + QCOMPARE(QGuiApplication::focusWindow(), root.windowHandle()); + QCOMPARE(QApplication::focusWidget(), lineEdit); + QVERIFY(lineEdit->hasFocus()); + + // embedded window gets focused programmatically + embedded->requestActivate(); + QVERIFY(QTest::qWaitForWindowFocused(embedded)); + QVERIFY(container->hasFocus()); + QCOMPARE(QGuiApplication::focusWindow(), embedded); + QCOMPARE(QApplication::focusWidget(), container); + QVERIFY(!lineEdit->hasFocus()); +} + QTEST_MAIN(tst_QWindowContainer) #include "tst_qwindowcontainer.moc" |