diff options
author | Doris Verria <[email protected]> | 2024-04-09 13:56:51 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <[email protected]> | 2024-04-12 02:12:30 +0000 |
commit | d49aa4022b48996f40a24932932896e80da228cd (patch) | |
tree | 7a106512ac27280b7191befdd239c2338306a494 | |
parent | b47586c69ec7d44976f006bdfc5beba5c014880b (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.cpp | 20 | ||||
-rw-r--r-- | tests/auto/quick/qquickitem2/data/focusInScopeChanges.qml | 31 | ||||
-rw-r--r-- | tests/auto/quick/qquickitem2/tst_qquickitem.cpp | 53 |
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" |