From 3d58715e6f5b3251c563a9a4e1156517f48a96fe Mon Sep 17 00:00:00 2001 From: Michael Weghorn Date: Wed, 19 Jul 2023 07:52:33 +0200 Subject: [PATCH] a11y: Implement QAccessibleSelectionInterface for QAccessibleTabBar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So far, there was custom handling for exposing the selection of tab bars on Windows via UIA, s. QWindowsUiaSelectionProvider. Implement QAccessibleSelectionInterface for QAccessibleTabBar, so selection is exposed via the platform a11y bridge on all platforms. (This makes the custom handling for tab bars in QWindowsUiaSelectionProvider obsolete for qtbase's own classes, but there are 4 more uses of the QAccessible::PageTabList role in qtdeclarative that might need a closer look before dropping the custom handling from QWindowsUiaSelectionProvider.) For consistency, also set the selectable/selected state for the tab buttons (that are the selectable children of the tab bar). Sample use via AT-SPI on Linux using Accerciser to interact on the a11y level (check the current selection, then select the second tab): 1) run the qtabbar example (tests/manual/qtabbar) 2) start Accerciser 3) select the tab bar in Accerciser's tree view of the a11y hierarchy 4) query for currently selected item, switch selection from "Tab 0" to "Tab 1" by using the following commands in Accerciser's IPython console: In [10]: sel = acc.querySelection() In [11]: sel.nSelectedChildren Out[11]: 1 In [12]: sel.getSelectedChild(0) Out[12]: In [13]: sel.getSelectedChild(0).name Out[13]: 'Tab 0' In [14]: sel.selectChild(1) Out[14]: True In [15]: sel.getSelectedChild(0).name Out[15]: 'Tab 1' Equivalent on Windows using NVDA's Python console: 1) start NVDA 2) run the qtabbar example (tests/manual/qtabbar) 3) click on the first tab ("Tab 0") in the tab bar 4) press Numpad_insert+control+z to start the NVDA Python console and capture snapshot variables 5) query and use the interfaces using NVDA's Python console: >>> import UIAHandler >>> tabbar = focus.parent >>> selectionpattern2 = tabbar.UIAElement.GetCurrentPattern(10034).QueryInterface(UIAHandler.IUIAutomationSelectionPattern2) >>> selectionpattern2.CurrentFirstSelectedItem.CurrentName 'Tab 0' >>> selectionpattern2.CurrentLastSelectedItem.CurrentName 'Tab 0' >>> selectionpattern2.CurrentItemCount 1 >>> selectionitempattern = tabbar.children[1].UIAElement.GetCurrentPattern(10010).QueryInterface(UIAHandler.IUIAutomationSelectionItemPattern) >>> selectionitempattern.Select() 0 >>> selectionpattern2.CurrentFirstSelectedItem.CurrentName 'Tab 1' Fixes: QTBUG-104602 Change-Id: I49b05bb84852c86a2b8669d7843fe173caf28e18 Reviewed-by: Jan Arve Sæther --- src/widgets/accessible/complexwidgets.cpp | 64 +++++++++++++++++++++++ src/widgets/accessible/complexwidgets_p.h | 14 ++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/widgets/accessible/complexwidgets.cpp b/src/widgets/accessible/complexwidgets.cpp index d9f5a5ae81..77bf8504fa 100644 --- a/src/widgets/accessible/complexwidgets.cpp +++ b/src/widgets/accessible/complexwidgets.cpp @@ -78,7 +78,9 @@ public: } QAccessible::State s = parent()->state(); + s.selectable = true; s.focused = (m_index == m_parent->currentIndex()); + s.selected = s.focused; return s; } QRect rect() const override { @@ -181,6 +183,14 @@ QAccessibleTabBar::~QAccessibleTabBar() QAccessible::deleteAccessibleInterface(id); } +void *QAccessibleTabBar::interface_cast(QAccessible::InterfaceType t) +{ + if (t == QAccessible::SelectionInterface) { + return static_cast(this); + } + return QAccessibleWidget::interface_cast(t); +} + /*! Returns the QTabBar. */ QTabBar *QAccessibleTabBar::tabBar() const { @@ -258,6 +268,60 @@ QString QAccessibleTabBar::text(QAccessible::Text t) const return QString(); } +int QAccessibleTabBar::selectedItemCount() const +{ + if (tabBar()->currentIndex() >= 0) + return 1; + return 0; +} + +QList QAccessibleTabBar::selectedItems() const +{ + QList items; + QAccessibleInterface *selected = selectedItem(0); + if (selected) + items.push_back(selected); + return items; +} + +QAccessibleInterface* QAccessibleTabBar::selectedItem(int selectionIndex) const +{ + const int currentIndex = tabBar()->currentIndex(); + if (selectionIndex != 0 || currentIndex < 0) + return nullptr; + return child(currentIndex); +} + +bool QAccessibleTabBar::isSelected(QAccessibleInterface *childItem) const +{ + return childItem && selectedItem(0) == childItem; +} + +bool QAccessibleTabBar::select(QAccessibleInterface *childItem) +{ + const int childIndex = indexOfChild(childItem); + if (childIndex >= 0) { + tabBar()->setCurrentIndex(childIndex); + return true; + } + return false; +} + +bool QAccessibleTabBar::unselect(QAccessibleInterface *) +{ + return false; +} + +bool QAccessibleTabBar::selectAll() +{ + return false; +} + +bool QAccessibleTabBar::clear() +{ + return false; +} + #endif // QT_CONFIG(tabbar) #if QT_CONFIG(combobox) diff --git a/src/widgets/accessible/complexwidgets_p.h b/src/widgets/accessible/complexwidgets_p.h index 12535f21a0..5169aa8243 100644 --- a/src/widgets/accessible/complexwidgets_p.h +++ b/src/widgets/accessible/complexwidgets_p.h @@ -70,12 +70,14 @@ public: #endif // QT_CONFIG(scrollarea) #if QT_CONFIG(tabbar) -class QAccessibleTabBar : public QAccessibleWidget +class QAccessibleTabBar : public QAccessibleWidget, public QAccessibleSelectionInterface { public: explicit QAccessibleTabBar(QWidget *w); ~QAccessibleTabBar(); + void *interface_cast(QAccessible::InterfaceType t) override; + QAccessibleInterface *focusChild() const override; int childCount() const override; QString text(QAccessible::Text t) const override; @@ -83,6 +85,16 @@ public: QAccessibleInterface* child(int index) const override; int indexOfChild(const QAccessibleInterface *child) const override; + // QAccessibleSelectionInterface + int selectedItemCount() const override; + QList selectedItems() const override; + QAccessibleInterface* selectedItem(int selectionIndex) const override; + bool isSelected(QAccessibleInterface *childItem) const override; + bool select(QAccessibleInterface *childItem) override; + bool unselect(QAccessibleInterface *childItem) override; + bool selectAll() override; + bool clear() override; + protected: QTabBar *tabBar() const; mutable QHash m_childInterfaces;