UI Accessibility: expose painted (non-QWidget) items as accessible children via Accessible::Item#278
Open
rezabakhshilaktasaraei wants to merge 27 commits intodesktop-app:masterfrom
Conversation
abf672e to
2d0f6ad
Compare
ilya-fedin
reviewed
Jan 22, 2026
ui/accessible/ui_accessible_item.cpp
Outdated
|
|
||
| const auto index = _item->accessibilityIndex(); | ||
| if (index < 0) return false; | ||
| if (const auto rp = qobject_cast<Ui::RpWidget*>(widget)) { |
Contributor
There was a problem hiding this comment.
Suggested change
| if (const auto rp = qobject_cast<Ui::RpWidget*>(widget)) { | |
| if (const auto rp = dynamic_cast<Ui::RpWidget*>(widget)) { |
ilya-fedin
reviewed
Jan 22, 2026
ui/accessible/ui_accessible_item.cpp
Outdated
| if (!_item) return QString(); | ||
|
|
||
| const auto widget = _item->accessibilityParentWidget(); | ||
| const auto rp = widget ? qobject_cast<Ui::RpWidget*>(widget) : nullptr; |
Contributor
There was a problem hiding this comment.
Suggested change
| const auto rp = widget ? qobject_cast<Ui::RpWidget*>(widget) : nullptr; | |
| const auto rp = dynamic_cast<Ui::RpWidget*>(widget); |
ilya-fedin
reviewed
Jan 22, 2026
ui/rp_widget.h
Outdated
Comment on lines
399
to
404
| virtual QAccessibleInterface* accessibilityChildInterface(int index) const { | ||
| return nullptr; | ||
| } | ||
| virtual QString accessibilityChildName(int index) const { return QString(); } | ||
| virtual QString accessibilityChildDescription(int index) const { return QString(); } | ||
| virtual QString accessibilityChildValue(int index) const { return QString(); } |
Contributor
There was a problem hiding this comment.
Why those are declared in .h unlike others?
- Introduce Ui::Accessible::Item and AccessibleItemWrap to expose non-QWidget / non-QObject items via QAccessibleInterface - Switch Ui::Accessible::Widget custom child handling to RpWidget::accessibilityChildInterface(index) - Implement hit-testing via Widget::childAt(x, y) for custom accessibility children - Improve focusChild() by returning the custom child whose state is focused or selected - Add RpWidget hooks for child metadata (accessibilityChildName/Description/Value) - Remove legacy RpWidget child APIs (accessibilityChildAt / accessibilityIndexOfChild / accessibilityFocusChild) - Update CMakeLists.txt to compile the new accessible item sources
Remove A11y debug LOG statements and add re-entrancy guard for focusChild(), HasEffectiveFocus helper for focus proxy detection, and accessibilityFocusedChildIndex() support for faster focused child lookup. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive accessibility support for virtual lists with screen readers: - Implement VirtualListCallbacks struct for list data callbacks - Add VirtualListFocusProxy widget for keyboard focus management - Implement VirtualListAccessible for Qt accessibility interface - Add VirtualListItemAccessible for individual list items - Support Home/End/Up/Down arrow key navigation - Add debounced announcements to prevent screen reader flooding - Use single focus proxy per list with proper event firing - Support dynamic row positions and heights - Binary search for childAt() hit testing - Direct index-to-item mapping for stable accessibility references Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Convert 4-space indentation to tabs - Add standard license header - Add braces to single-line if statements - Fix extra indentation bug on return statement Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add optional itemRole callback to VirtualListCallbacks - Support QAccessible::RadioButton role for radio button lists - Add checkable/checked state for radio button items - Announce focus when focus proxy receives focus Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove all state computation from accessible_virtual_list.cpp - Make VirtualListItemAccessible::state() a simple passthrough - Make VirtualListFocusProxyAccessible::state() a simple passthrough - Remove itemRole callback, use widget's accessibilityChildRole() instead - Add accessibilityChildRole(), accessibilityChildState() to RpWidget - Move accessibilityChild* methods from inline to declaration + impl Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Keep state handling inside accessible_virtual_list.cpp rather than delegating to widget classes. This fixes Tab focus not working for the language list which is deeply nested in the widget hierarchy. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move state logic from accessible_virtual_list.cpp to widget classes. Now VirtualListItemAccessible and VirtualListFocusProxyAccessible delegate to widget's accessibilityChildState() for base state and only compute focused state locally (requires access to internal focus proxy). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…classes - Remove role(), state(), text() overrides from VirtualListAccessible - Pass role to QAccessibleWidget constructor from widget->accessibilityRole() - Remove listName callback from VirtualListCallbacks - Add readOnly field to AccessibilityState struct Widget classes now define their own role via accessibilityRole(), name via setAccessibleName(), and state via accessibilityState(). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove rowName callback from VirtualListCallbacks - Use widget->accessibilityChildName(index) as passthrough instead Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ments Announce screen reader updates immediately without the 50ms delay. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…proxy Move keyboard navigation responsibility to widget classes. VirtualListCallbacks now only contains geometry/selection callbacks. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Simplify virtual list accessibility by removing the per-item accessible objects. The focus proxy alone provides sufficient accessibility as it represents the currently selected item to screen readers. - Remove VirtualListItemAccessible class (~85 lines) - Update VirtualListAccessible to only return focus proxy for selected index - Remove getOrCreateItem() method and _items cache - Keep VirtualListAccessible for container semantics (childCount, role) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add VirtualListItemProxy class for NVDA Insert+4/6 object navigation. Created on-demand without caching, ~50 lines vs ~85 in original. - Focus proxy used for selected index (has focus state) - Item proxy used for other indices (object navigation) - Binary search restored in childAt() for hit testing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Make ui_accessible_item use RpWidget methods consistently: - role() now uses rp->accessibilityChildRole() - state() now uses rp->accessibilityChildState(index) only - rect() now uses rp->accessibilityChildRect(index) Remove unused methods from AccessibleItemWrap: - accessibilityRole() - accessibilityState() - accessibilityRect() Add accessibilityChildRect(int index) to RpWidget for child geometry. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove accessibilityFocusedChildIndex() optimization in favor of Qt's standard approach - iterate through children checking state(). - Remove accessibilityFocusedChildIndex() from RpWidget - Remove fast path from Widget::focusChild() - Use iteration to find focused child (Qt standard pattern) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Screen readers like NVDA need list context (name, item count) when focusing list items. Previously, focus events were sent on the proxy widget directly, which didn't provide list container information. Now send focus events on the parent list with setChild(index), matching the approach used in dialogs_inner_widget. This allows screen readers to announce "List name, list, X items" when entering a list. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move implementation from inline in header to cpp file, matching the pattern used by other accessibility methods in RpWidget. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Focus event on parent with setChild() provides list context when entering a list, but arrow key navigation within the list needs the NameChanged event on the focus proxy to announce item names. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add parentRp() helper to reduce repeated qobject_cast calls - Simplify isValid() with early returns - Use switch statement in text() like ui_accessible_widget - Consolidate null checks using helper method - Add comments for clarity (object() returns nullptr for virtual items) - Remove unused QWidget include - Reorder methods to match QAccessibleInterface declaration order Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Ensure ui_accessible_item and ui_accessible_widget follow consistent patterns: - Add section comments (Identity, Content, Children, Navigation, Actions) - Reorder methods to match logical grouping - Use braces in switch cases consistently - Align header declarations with implementation order Both files now follow the same organizational structure making them easier to maintain and compare. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ctly Remove AccessibleItemWrap interface entirely. Item now takes (RpWidget* parent, int index) directly, like VirtualListItemProxy. - Remove AccessibleItemWrap class - Item stores _parent and _index directly - Add setIndex() for updating index when items reorder - Rename indexInParent() to index() This is simpler and matches how VirtualListItemProxy works. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove redundant comments, unnecessary braces from switch cases, and align section comments with Widget for consistency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add accessibilityChildNameChanged, accessibilityChildDescriptionChanged, accessibilityChildValueChanged, accessibilityChildStateChanged, and accessibilityChildFocused to RpWidget for virtual/painted items. Make selected, focused, and focusable fields in AccessibilityState use tri-state int (-1 = don't override) so writeTo doesn't clobber Qt's tracked state when only other fields change. Fix typo in AccessibilityState comment, add [[nodiscard]] to child accessor methods. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All consumers migrated to Ui::Accessible::Item. Remove the VirtualList infrastructure (focus proxy, registries, factories) and clean up the accessibility factory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…State Change selected from tri-state int back to bool so it can be easily overridden with designated initializers. Keep focused and focusable as tri-state since they need "don't override" semantics. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2d0f6ad to
525ed04
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR adds accessibility support for painted UI elements that are not real QWidgets (i.e., items drawn inside a widget). The goal is to make these “virtual” elements appear in the accessibility tree so screen readers can discover, navigate, and interact with them.
Summary
•
Introduces Ui::Accessible::Item (QAccessibleInterface) plus AccessibleItemWrap to represent non-QWidget / non-QObject painted items as accessibility nodes.
•
Extends RpWidget with a simple API to expose painted items as accessibility children via accessibilityChildInterface(index), along with optional text hooks:
•
accessibilityChildName(int)
•
accessibilityChildDescription(int)
•
accessibilityChildValue(int)
•
Updates Ui::Accessible::Widget to use the new interface-based child API and support:
•
correct child lookup (child(index))
•
hit-testing for painted items (childAt(x, y))
•
improved focus reporting (focusChild() returns the focused/selected painted item when the widget has focus)
•
Removes the old widget-only child accessibility methods on RpWidget (accessibilityChildAt, accessibilityIndexOfChild, accessibilityFocusChild) since they don’t fit painted-item scenarios.
Why
A lot of our UI is rendered manually (custom painting) rather than composed from actual Qt widgets. Without explicit QAccessibleInterface objects, those elements are invisible to assistive technologies. This change provides a clean path to expose painted items with proper role, state, geometry, and text, without requiring them to be real widgets.
Notes
•
Item geometry is mapped to global coordinates for accurate screen reader hit-testing.
•
Validation ensures the parent widget exists and indices are within bounds when a child count is provided.
Testing
•
Manual verification with a screen reader:
•
painted items are discoverable in the accessibility tree
•
hit-testing works (childAt returns the correct item)
•
focus/selection is reflected correctly for painted items