summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDoris Verria <[email protected]>2024-03-26 15:57:56 +0100
committerDoris Verria <[email protected]>2024-04-22 16:01:01 +0200
commitf20be43b827ffa5c9361bc128fd930841435bdf6 (patch)
tree316107f38fb6b46686dadcf0da0869ba6171b70c
parentd06cff08598129c85952f4d45a382d300cb4ac65 (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.cpp4
-rw-r--r--src/widgets/kernel/qwindowcontainer.cpp40
-rw-r--r--src/widgets/kernel/qwindowcontainer_p.h3
-rw-r--r--tests/auto/widgets/kernel/qwindowcontainer/tst_qwindowcontainer.cpp62
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"