From bc9b03a43b20c7ea97886061b3e74ac8c4a12c5e Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Mon, 7 Nov 2022 12:45:09 +0100 Subject: [PATCH] Windows: Inform accessibility system about the focused child item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a complex object (i.e. one with children that are themselves not fully exposed objects) gets focus, then we need to inform the accessibility system about which child object actually has focus. This was only done for item views, but not for other complex widgets. An editable QComboBoxes is the focus proxy for its line edit. The line edit never gets focus itself (QComboBox forwards relevant events), and is the accessible child item with index 1. So when an editable combobox gets focus, it needs to raise the automation event for the line edit child. Implement QAccessibleComboBox::focusChild to return the interface to the lineedit for editable comboboxes so that the UI Automation bridge can correctly notify about the focus being moved to an editable text input field. Fixes: QTBUG-107572 Change-Id: Id60e2791ec859365255baa9bfd01547979cd2b44 Reviewed-by: Jan Arve Sæther (cherry picked from commit b58876c296a5a87f50d5e554afc277e5bc752a16) Reviewed-by: Qt Cherry-pick Bot --- .../uiautomation/qwindowsuiamainprovider.cpp | 5 ++- src/widgets/accessible/complexwidgets.cpp | 10 +++++ src/widgets/accessible/complexwidgets_p.h | 1 + .../qaccessibility/tst_qaccessibility.cpp | 41 +++++++++++++++++++ 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp index 03cac60e081..cb35ed398c5 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp @@ -71,10 +71,11 @@ QWindowsUiaMainProvider::~QWindowsUiaMainProvider() void QWindowsUiaMainProvider::notifyFocusChange(QAccessibleEvent *event) { if (QAccessibleInterface *accessible = event->accessibleInterface()) { - // If this is a table/tree/list, raise event for the focused cell/item instead. - if (accessible->tableInterface()) + // If this is a complex element, raise event for the focused child instead. + if (accessible->childCount()) { if (QAccessibleInterface *child = accessible->focusChild()) accessible = child; + } if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_AutomationFocusChangedEventId); } diff --git a/src/widgets/accessible/complexwidgets.cpp b/src/widgets/accessible/complexwidgets.cpp index 3d5154a30fc..87214cc7dab 100644 --- a/src/widgets/accessible/complexwidgets.cpp +++ b/src/widgets/accessible/complexwidgets.cpp @@ -320,6 +320,16 @@ int QAccessibleComboBox::indexOfChild(const QAccessibleInterface *child) const return -1; } +QAccessibleInterface *QAccessibleComboBox::focusChild() const +{ + // The editable combobox is the focus proxy of its lineedit, so the + // lineedit itself never gets focus. But it is the accessible focus + // child of an editable combobox. + if (comboBox()->isEditable()) + return child(1); + return nullptr; +} + /*! \reimp */ QString QAccessibleComboBox::text(QAccessible::Text t) const { diff --git a/src/widgets/accessible/complexwidgets_p.h b/src/widgets/accessible/complexwidgets_p.h index 1fd5d5aeebc..12535f21a04 100644 --- a/src/widgets/accessible/complexwidgets_p.h +++ b/src/widgets/accessible/complexwidgets_p.h @@ -99,6 +99,7 @@ public: QAccessibleInterface *childAt(int x, int y) const override; int indexOfChild(const QAccessibleInterface *child) const override; QAccessibleInterface* child(int index) const override; + QAccessibleInterface* focusChild() const override; QString text(QAccessible::Text t) const override; diff --git a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp index a84c6dc0a0d..87c6810365f 100644 --- a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp +++ b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp @@ -4247,6 +4247,47 @@ void tst_QAccessibility::focusChild() QVERIFY(child); QCOMPARE(child->text(QAccessible::Name), QStringLiteral("Klimt")); } + { + QWidget window; + // takes the initial focus + QLineEdit lineEdit; + QComboBox comboBox; + comboBox.addItems({"One", "Two", "Three"}); + QComboBox editableComboBox; + editableComboBox.setEditable(true); + editableComboBox.addItems({"A", "B", "C"}); + QVBoxLayout vbox; + vbox.addWidget(&lineEdit); + vbox.addWidget(&comboBox); + vbox.addWidget(&editableComboBox); + window.setLayout(&vbox); + + window.show(); + QVERIFY(QTest::qWaitForWindowExposed(&window)); + QTestAccessibility::clearEvents(); + QAccessibleInterface *iface = nullptr; + + comboBox.setFocus(); + { + QAccessibleEvent focusEvent(&comboBox, QAccessible::Focus); + QVERIFY(QTestAccessibility::containsEvent(&focusEvent)); + } + iface = QAccessible::queryAccessibleInterface(&comboBox); + QVERIFY(iface); + QCOMPARE(iface->focusChild(), nullptr); + + editableComboBox.setFocus(); + // Qt updates about the editable combobox, not the lineedit, as the + // combobox is the lineedit's focus proxy. + { + QAccessibleEvent focusEvent(&editableComboBox, QAccessible::Focus); + QVERIFY(QTestAccessibility::containsEvent(&focusEvent)); + } + iface = QAccessible::queryAccessibleInterface(&editableComboBox); + QVERIFY(iface); + QVERIFY(iface->focusChild()); + QCOMPARE(iface->focusChild()->role(), QAccessible::EditableText); + } } void tst_QAccessibility::messageBoxTest_data()