Expose discrete slider sections as accessible tabs - #31097
Expose discrete slider sections as accessible tabs#31097rezabakhshilaktasaraei wants to merge 2 commits into
Conversation
1ce7725 to
314847c
Compare
| QAccessible::State state() const override { | ||
| auto result = Accessible::Widget::state(); | ||
| if (rp()->accessibilityChildCount() > 0) { | ||
| result.focused = false; |
There was a problem hiding this comment.
That doesn't look right. This means Qt wil report no focused widget when this widget has focus since its internal state will think the widget is in focus thus reporting focused = true only for it in the window but then you override it and screen reader will get focused = false for all widgets in the window.
There was a problem hiding this comment.
You were right, and it was worse than it looked: the slider really does hold keyboard focus, so the override was reporting a lie about it. It is gone from the branch.
What it was papering over is a platform gap. On Qt 5.15 the Windows bridge redirects a focus event to focusChild() only for an accessible that exposes a table interface, so focus landed on the container instead of the section focusChild() points at. Upstream generalized that condition to any element with children in 6.2; desktop-app/patches#263 backports it. With that in place the container reports focused, as it should, and the section is what gets announced.
|
|
||
| // The slider holds real keyboard focus for its virtual (painted) sections, | ||
| // so Qt would report the container itself as focused. Report it focusable | ||
| // only, like the folders list container, and let focusChild() carry the |
There was a problem hiding this comment.
I don't see how it could work? If you don't want the widget to get focus, you either:
- Set no focus policy
- Set a focus proxy (QWidget::setFocusProxy)
- Call setFocus to some other widget on focus event
There was a problem hiding this comment.
Sorry, the comment above it was misleading. The widget does want the focus: it handles the arrows and is the list's single Tab stop, so none of the three options applies. All I was after was which element the screen reader announces once focus is there - the browsed section rather than the container - and the state override was simply the wrong tool for that. It is gone.
focusChild() already points at the browsed section; what was missing was the platform raising the focus event on it, which desktop-app/patches#263 fixes.
558dcd8 to
76cd017
Compare
771493d to
d9d2b0e
Compare
The sections of Ui::DiscreteSlider are painted, so screen readers had nothing to focus or read. In screen reader mode the slider becomes a Tab stop reporting a tab control with a virtual tab per section: Tab lands on the active tab, the arrows and Home/End switch to the tab they land on right away, like the native Windows tab controls, and the platform announces the switch through the focus move. The selection interface carries the selected state, and subclasses can follow the browse position through a producer and an accessor. Without a screen reader nothing changes - the widget is not focusable then.
a81aa52 to
24a0663
Compare
The tab-like switchers built on Ui::DiscreteSlider / SettingsSlider (search result tabs, stickers box tabs, emoji panel tabs, message options box tabs and so on) were invisible to screen readers: the sections are painted, so there was nothing to focus or read.
The slider now reports itself as a tab control with one virtual tab per section - the UI calls these controls tabs, so the screen reader calls them that too. In screen reader mode the widget becomes a Tab stop: Tab lands on the active tab, and the arrows (mirrored in RTL) and Home/End switch right away to the tab they land on, the way native Windows tab controls behave - the switch is announced through the focus move, so the screen reader reads the tab it landed on. UIA SetFocus / Invoke are dispatched by a stable per-section identity on the main thread.
Not every strip wants that: where switching loads content or can be refused, setAccessibilityActivateOnBrowse(false) keeps a browse-then-commit model - the arrows only move the reading position and announce the tab with its selected state, Enter or Space activates. The search scope tabs (#31184) and the chat folder tabs (#31098) use it. For such subclasses the browse position is exposed as an rpl producer (accessibilitySectionBrowsed), so a scrollable strip can bring the browsed tab into view, activation goes through a protected virtual (activateSectionByAccessibility), so locked premium folders can be intercepted, and a protected accessor (accessibilityBrowsedSection) lets a keyboard-invoked context menu anchor on the browsed tab - a menu asked for from the keyboard arrives with the center of an empty input method rect, so a position based lookup can't find the right one.
Three things outside this repo are needed on Windows, all about what the Qt 5 UIA bridge does and doesn't forward:
Tested with NVDA on Windows 10 with all three applied: Tab lands on the active tab and it is announced once, and the arrows switch and announce the tab they land on, exactly like the native Windows tab controls. Without a screen reader nothing changes - the widget is not focusable outside screen reader mode.