aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDoris Verria <[email protected]>2024-04-09 13:56:51 +0200
committerQt Cherry-pick Bot <[email protected]>2024-04-12 02:12:30 +0000
commitd49aa4022b48996f40a24932932896e80da228cd (patch)
tree7a106512ac27280b7191befdd239c2338306a494
parentb47586c69ec7d44976f006bdfc5beba5c014880b (diff)
QQuickItem::setFocus: Don't return too early
a8be17a1472c6f504c0c40f68fdc13df035ac4b4 introduced a workaround for setFocus returning too early when trying to set the active focus in certain reparenting scenarios. In this workaround we check if, after we try to setFocus() to the item and its ancestors, the item still doesn't have activeFocus, then we try to set it again in the delivery agent. However, it can happen that an item loses activeFocus at some point during this execution. This is the case for example when the item has an "onActiveFocusChanged" signal handler that calls forceActiveFocus() on another item. So we can't always expect the item to have activeFocus in the end. As a fix, we should instead try to not return too early from QQuickItem::setFocus. Currently, we return early if item->focus == focus. However, this is not enough of a check. When we set focus to an item in Quick, we set the focus in a scope, an ancestor focus scope. So we need to check both the focus flag AND that the item is the scoped focus item of its nearest ancestor before we return early. Amends a8be17a1472c6f504c0c40f68fdc13df035ac4b4. Fixes: QTBUG-120670 Pick-to: 6.5 Change-Id: I3f55f828d57fdc7d6f628813aa37930bdec0f994 Reviewed-by: Shawn Rutledge <[email protected]> (cherry picked from commit 23691ba87219ca82df3a75a74af4dab13d86a654) Reviewed-by: Qt Cherry-pick Bot <[email protected]>
-rw-r--r--src/quick/items/qquickitem.cpp20
-rw-r--r--tests/auto/quick/qquickitem2/data/focusInScopeChanges.qml31
-rw-r--r--tests/auto/quick/qquickitem2/tst_qquickitem.cpp53
3 files changed, 90 insertions, 14 deletions
diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp
index e66bea04d8..eeb6581ca6 100644
--- a/src/quick/items/qquickitem.cpp
+++ b/src/quick/items/qquickitem.cpp
@@ -4965,7 +4965,6 @@ void QQuickItem::forceActiveFocus()
void QQuickItem::forceActiveFocus(Qt::FocusReason reason)
{
- Q_D(QQuickItem);
setFocus(true, reason);
QQuickItem *parent = parentItem();
QQuickItem *scope = nullptr;
@@ -4977,14 +4976,6 @@ void QQuickItem::forceActiveFocus(Qt::FocusReason reason)
}
parent = parent->parentItem();
}
- // In certain reparenting scenarios, d->focus might be true and the scope
- // might also have focus, so that setFocus() returns early without actually
- // acquiring active focus, because it thinks it already has it. In that
- // case, try to set the DeliveryAgent's active focus. (QTBUG-89736).
- if (scope && !d->activeFocus) {
- if (auto da = d->deliveryAgentPrivate())
- da->setFocusInScope(scope, this, Qt::OtherFocusReason);
- }
}
/*!
@@ -7870,15 +7861,16 @@ void QQuickItem::setFocus(bool focus)
void QQuickItem::setFocus(bool focus, Qt::FocusReason reason)
{
Q_D(QQuickItem);
- if (d->focus == focus)
+ // Need to find our nearest focus scope
+ QQuickItem *scope = parentItem();
+ while (scope && !scope->isFocusScope() && scope->parentItem())
+ scope = scope->parentItem();
+
+ if (d->focus == focus && (!focus || !scope || QQuickItemPrivate::get(scope)->subFocusItem == this))
return;
bool notifyListeners = false;
if (d->window || d->parentItem) {
- // Need to find our nearest focus scope
- QQuickItem *scope = parentItem();
- while (scope && !scope->isFocusScope() && scope->parentItem())
- scope = scope->parentItem();
if (d->window) {
auto da = d->deliveryAgentPrivate();
Q_ASSERT(da);
diff --git a/tests/auto/quick/qquickitem2/data/focusInScopeChanges.qml b/tests/auto/quick/qquickitem2/data/focusInScopeChanges.qml
new file mode 100644
index 0000000000..3bf765a29d
--- /dev/null
+++ b/tests/auto/quick/qquickitem2/data/focusInScopeChanges.qml
@@ -0,0 +1,31 @@
+import QtQuick
+
+Item {
+ id: main
+ objectName: "main"
+ width: 800
+ height: 600
+
+ FocusScope {
+ objectName: "focusScope"
+
+ Column {
+ Rectangle {
+ id: rectangle
+ focus: true
+ objectName: "rect"
+ width: textInput.width
+ height: textInput.height
+ border.width: 1
+ onActiveFocusChanged: textInput.forceActiveFocus()
+ }
+
+ TextInput {
+ id: textInput
+ objectName: "textInput"
+ font.pixelSize: 40
+ text: "focus me"
+ }
+ }
+ }
+}
diff --git a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp
index e96e679d7d..1b631810aa 100644
--- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp
+++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp
@@ -130,6 +130,7 @@ private slots:
void visibleChanged();
void lastFocusChangeReason();
+ void focusInScopeChanges();
private:
QQmlEngine engine;
@@ -4306,6 +4307,58 @@ void tst_QQuickItem::lastFocusChangeReason()
QCOMPARE(customItemPrivate->lastFocusChangeReason(), Qt::MouseFocusReason);
}
+void tst_QQuickItem::focusInScopeChanges()
+{
+ std::unique_ptr<QQuickView> window = std::make_unique<QQuickView>();
+ window->setSource(testFileUrl("focusInScopeChanges.qml"));
+ window->show();
+ window->requestActivate();
+ QVERIFY(QTest::qWaitForWindowFocused(window.get()));
+
+ QQuickItem *main = window->rootObject();
+ QVERIFY(main);
+ QQuickItem *focusScope = main->findChild<QQuickItem *>("focusScope");
+ QQuickItem *rect = main->findChild<QQuickItem *>("rect");
+ QQuickItem *textInput = main->findChild<QQuickItem *>("textInput");
+
+ QVERIFY(focusScope);
+ QVERIFY(rect);
+ QVERIFY(textInput);
+ QVERIFY(window->contentItem());
+
+ QSignalSpy fsActiveFocusSpy(focusScope, SIGNAL(activeFocusChanged(bool)));
+ QSignalSpy rectActiveFocusSpy(rect, SIGNAL(activeFocusChanged(bool)));
+ QSignalSpy textInputActiveFocusSpy(textInput, SIGNAL(activeFocusChanged(bool)));
+
+ // The window's content item will have activeFocus if window is focused
+ QTRY_VERIFY(window->contentItem()->hasActiveFocus());
+
+ QVERIFY(!focusScope->hasActiveFocus());
+ QVERIFY(!rect->hasActiveFocus());
+ QVERIFY(!textInput->hasActiveFocus());
+ QCOMPARE(fsActiveFocusSpy.size(), 0);
+ QCOMPARE(rectActiveFocusSpy.size(), 0);
+ QCOMPARE(textInputActiveFocusSpy.size(), 0);
+
+ // setting focus to rect shouldn't affect activeFocus as long as its
+ // parent focus scope doesn't have the activeFocus
+ rect->setFocus(true);
+ QCOMPARE(fsActiveFocusSpy.size(), 0);
+ QCOMPARE(rectActiveFocusSpy.size(), 0);
+ QCOMPARE(textInputActiveFocusSpy.size(), 0);
+
+ // focusScope is the only child with focus in the parent
+ // scope, so it will gain activeFocus
+ focusScope->setFocus(true);
+ QCOMPARE(fsActiveFocusSpy.size(), 1);
+ QVERIFY(fsActiveFocusSpy.first().at(0).toBool());
+ // rect loses activeFocus because textInput gains it (as a result of code in signal handler)
+ QCOMPARE(rectActiveFocusSpy.size(), 2);
+ QVERIFY(!rect->hasActiveFocus());
+ QCOMPARE(textInputActiveFocusSpy.size(), 1);
+ QVERIFY(textInput->hasActiveFocus());
+}
+
QTEST_MAIN(tst_QQuickItem)
#include "tst_qquickitem.moc"