-
Notifications
You must be signed in to change notification settings - Fork 238
Try to fix QTreeWidget accessibility bug #3573
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d148bc1
46c45a3
867e87c
6101375
e6d543a
1d39c1d
2ba71f7
0be5b53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -215,6 +215,90 @@ CConnectDlg::CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteR | |
| // per default the root shall not be decorated (to save space) | ||
| lvwServers->setRootIsDecorated ( false ); | ||
|
|
||
| #ifdef USE_ACCESSIBLE_SERVER_LIST | ||
| // Create simplified accessible navigation panel for screen readers | ||
| // Design: One info label + 2 navigation buttons (Previous/Next) + Toggle button | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will add screenshot of macOS version |
||
|
|
||
| wAccessibleNavPanel = new QWidget ( this ); | ||
| wAccessibleNavPanel->setObjectName ( "wAccessibleNavPanel" ); | ||
|
|
||
| QVBoxLayout* accessibleMainLayout = new QVBoxLayout ( wAccessibleNavPanel ); | ||
| accessibleMainLayout->setContentsMargins ( 0, 5, 0, 5 ); | ||
|
|
||
| // Create horizontal layout for navigation buttons (Previous, Next) | ||
| QHBoxLayout* navLayout = new QHBoxLayout(); | ||
|
|
||
| butAccessiblePrevious = new QPushButton ( u8"\u2190 " + tr ( "Previous Server" ), wAccessibleNavPanel ); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unicode magic is not nice.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, this does not go to the previous server, but rather to the previous entry - so it could also be a musician.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a minimum, the unicode characters want putting into constants, named so someone reading the code gets some idea of what they represent when rendered.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if we even need those characters. |
||
| butAccessiblePrevious->setObjectName ( "butAccessiblePrevious" ); | ||
| butAccessiblePrevious->setAccessibleName ( tr ( "Go to previous server" ) ); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, "Previous Button" and "Next Button" would be better for these, I think. |
||
| butAccessiblePrevious->setAccessibleDescription ( tr ( "Navigate to the previous server in the list" ) ); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then these can be the more vague but more accurate "Moves to the previous server list entry" and "Moves to the next server list entry" |
||
| butAccessiblePrevious->setShortcut ( QKeySequence ( Qt::ALT | Qt::Key_Up ) ); | ||
| butAccessiblePrevious->setToolTip ( tr ( "Previous server (Alt+Up)" ) ); | ||
| navLayout->addWidget ( butAccessiblePrevious, 1 ); | ||
|
|
||
| butAccessibleNext = new QPushButton ( tr ( "Next Server" ) + u8" \u2192", wAccessibleNavPanel ); | ||
| butAccessibleNext->setObjectName ( "butAccessibleNext" ); | ||
| butAccessibleNext->setAccessibleName ( tr ( "Go to next server" ) ); | ||
| butAccessibleNext->setAccessibleDescription ( tr ( "Navigate to the next server in the list" ) ); | ||
| butAccessibleNext->setShortcut ( QKeySequence ( Qt::ALT | Qt::Key_Down ) ); | ||
| butAccessibleNext->setToolTip ( tr ( "Next server (Alt+Down)" ) ); | ||
| navLayout->addWidget ( butAccessibleNext, 1 ); | ||
|
|
||
| accessibleMainLayout->addLayout ( navLayout ); | ||
|
|
||
| // Create read-only, focusable label showing current server information | ||
| // Using QLabel with focus policy so screenreaders can read it | ||
| lblAccessibleServerInfo = new QLabel ( tr ( "No server selected" ), wAccessibleNavPanel ); | ||
| lblAccessibleServerInfo->setObjectName ( "lblAccessibleServerInfo" ); | ||
| lblAccessibleServerInfo->setWordWrap ( true ); | ||
| lblAccessibleServerInfo->setFrameStyle ( QFrame::Panel | QFrame::Sunken ); | ||
| lblAccessibleServerInfo->setTextInteractionFlags ( Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard ); | ||
| lblAccessibleServerInfo->setMinimumHeight ( 50 ); | ||
| lblAccessibleServerInfo->setFocusPolicy ( Qt::StrongFocus ); // Make it focusable for screen readers | ||
| lblAccessibleServerInfo->setAccessibleName ( tr ( "Current server information" ) ); | ||
| lblAccessibleServerInfo->setAccessibleDescription ( tr ( "Shows details of the currently selected server. Use Previous/Next buttons or Alt+Up/Down to navigate." ) ); | ||
| accessibleMainLayout->addWidget ( lblAccessibleServerInfo ); | ||
|
|
||
| // Create toggle button | ||
| butToggleAccessible = new QPushButton ( u8"\u25BC " + tr ( "Hide Accessible Controls" ), this ); | ||
| butToggleAccessible->setObjectName ( "butToggleAccessible" ); | ||
| butToggleAccessible->setAccessibleName ( tr ( "Toggle accessible controls" ) ); | ||
| butToggleAccessible->setAccessibleDescription ( tr ( "Show or hide the accessible navigation panel for screen readers" ) ); | ||
| butToggleAccessible->setCheckable ( true ); | ||
| butToggleAccessible->setChecked ( true ); | ||
| butToggleAccessible->setToolTip ( tr ( "Toggle accessible controls" ) ); | ||
|
|
||
| // Insert the accessible panel and toggle button into the layout right after the tree widget | ||
| QVBoxLayout* mainLayout = qobject_cast<QVBoxLayout*> ( layout() ); | ||
| if ( mainLayout ) | ||
| { | ||
| // Find the tree widget in the layout | ||
| bool inserted = false; | ||
| for ( int i = 0; i < mainLayout->count(); ++i ) | ||
| { | ||
| QLayoutItem* item = mainLayout->itemAt ( i ); | ||
| if ( item && item->widget() == lvwServers ) | ||
| { | ||
| mainLayout->insertWidget ( i + 1, butToggleAccessible ); | ||
| mainLayout->insertWidget ( i + 2, wAccessibleNavPanel ); | ||
| inserted = true; | ||
| break; | ||
| } | ||
| } | ||
| if ( !inserted ) | ||
| { | ||
| qWarning ( "Accessible navigation panel could not be inserted: tree widget not found in layout." ); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| qWarning ( "Accessible navigation panel could not be inserted: main layout cast failed." ); | ||
| } | ||
|
|
||
| // Initially show the accessible panel | ||
| wAccessibleNavPanel->setVisible ( true ); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using the setting, you'd set to false immediate after creating and based on the setting here. |
||
| #endif | ||
|
|
||
| // make sure the connect button has the focus | ||
| butConnect->setFocus(); | ||
|
|
||
|
|
@@ -243,6 +327,9 @@ CConnectDlg::CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteR | |
| // to get default return key behaviour working | ||
| QObject::connect ( lvwServers, &QTreeWidget::activated, this, &CConnectDlg::OnConnectClicked ); | ||
|
|
||
| // connect selection change for accessibility support | ||
| QObject::connect ( lvwServers, &QTreeWidget::itemSelectionChanged, this, &CConnectDlg::OnServerListItemSelectionChanged ); | ||
|
|
||
| // line edit | ||
| QObject::connect ( edtFilter, &QLineEdit::textEdited, this, &CConnectDlg::OnFilterTextEdited ); | ||
|
|
||
|
|
@@ -266,6 +353,13 @@ CConnectDlg::CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteR | |
| QObject::connect ( &TimerPing, &QTimer::timeout, this, &CConnectDlg::OnTimerPing ); | ||
|
|
||
| QObject::connect ( &TimerReRequestServList, &QTimer::timeout, this, &CConnectDlg::OnTimerReRequestServList ); | ||
|
|
||
| #ifdef USE_ACCESSIBLE_SERVER_LIST | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the widget exists (even if not shown), these need not be compile-time. |
||
| // accessible navigation panel | ||
| QObject::connect ( butAccessiblePrevious, &QPushButton::clicked, this, &CConnectDlg::OnAccessiblePreviousClicked ); | ||
| QObject::connect ( butAccessibleNext, &QPushButton::clicked, this, &CConnectDlg::OnAccessibleNextClicked ); | ||
| QObject::connect ( butToggleAccessible, &QPushButton::clicked, this, &CConnectDlg::OnToggleAccessibleClicked ); | ||
| #endif | ||
| } | ||
|
|
||
| void CConnectDlg::showEvent ( QShowEvent* ) | ||
|
|
@@ -631,6 +725,196 @@ void CConnectDlg::OnServerAddrEditTextChanged ( const QString& ) | |
| lvwServers->clearSelection(); | ||
| } | ||
|
|
||
| void CConnectDlg::OnServerListItemSelectionChanged() | ||
| { | ||
| #ifdef USE_ACCESSIBLE_SERVER_LIST | ||
| UpdateAccessibleServerInfo(); | ||
| #endif | ||
| } | ||
|
|
||
| #ifdef USE_ACCESSIBLE_SERVER_LIST | ||
| void CConnectDlg::UpdateAccessibleServerInfo() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| { | ||
| QList<QTreeWidgetItem*> selectedItems = lvwServers->selectedItems(); | ||
|
|
||
| if ( !selectedItems.isEmpty() && selectedItems.first()->parent() == nullptr ) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to comment this to explain it has to handle Server entries and Client entries separately. |
||
| { | ||
| // We have a server item selected (not a musician child item) | ||
| QTreeWidgetItem* pItem = selectedItems.first(); | ||
|
|
||
| // Extract server information | ||
| QString serverName = pItem->text ( LVC_NAME ); | ||
| QString pingTime = pItem->text ( LVC_PING ); | ||
| QString musicians = pItem->text ( LVC_CLIENTS ); | ||
| QString location = pItem->text ( LVC_LOCATION ); | ||
| QString version = pItem->text ( LVC_VERSION ); | ||
|
|
||
| // Build text for screen readers | ||
| QString accessibleText = tr ( "Server: %1" ).arg ( serverName ); | ||
| if ( !pingTime.isEmpty() ) | ||
| { | ||
| accessibleText += tr ( ", Ping: %1" ).arg ( pingTime ); | ||
| } | ||
| if ( !musicians.isEmpty() ) | ||
| { | ||
| accessibleText += tr ( ", Musicians: %1" ).arg ( musicians ); | ||
| } | ||
| if ( !location.isEmpty() ) | ||
| { | ||
| accessibleText += tr ( ", Location: %1" ).arg ( location ); | ||
| } | ||
| if ( !version.isEmpty() ) | ||
| { | ||
| accessibleText += tr ( ", Version: %1" ).arg ( version ); | ||
| } | ||
|
|
||
| // Update label | ||
| lblAccessibleServerInfo->setText ( accessibleText ); | ||
| lblAccessibleServerInfo->setAccessibleName ( tr ( "Selected server information" ) ); | ||
| lblAccessibleServerInfo->setAccessibleDescription ( accessibleText ); | ||
|
|
||
| // Update navigation buttons to show previous/next server names | ||
| int currentIndex = lvwServers->indexOfTopLevelItem ( pItem ); | ||
|
|
||
| // Update "Previous" button with previous server name | ||
| if ( currentIndex > 0 ) | ||
| { | ||
| QTreeWidgetItem* prevItem = lvwServers->topLevelItem ( currentIndex - 1 ); | ||
| QString prevName = prevItem->text ( LVC_NAME ); | ||
| butAccessiblePrevious->setText ( u8"\u2190 " + prevName ); | ||
| butAccessiblePrevious->setAccessibleName ( tr ( "Previous server: %1" ).arg ( prevName ) ); | ||
| butAccessiblePrevious->setAccessibleDescription ( tr ( "Go to previous server: %1" ).arg ( prevName ) ); | ||
| butAccessiblePrevious->setEnabled ( true ); | ||
| } | ||
| else | ||
| { | ||
| butAccessiblePrevious->setText ( u8"\u2190 " + tr ( "(first)" ) ); | ||
| butAccessiblePrevious->setAccessibleName ( tr ( "No previous server - at first server" ) ); | ||
| butAccessiblePrevious->setAccessibleDescription ( tr ( "Cannot go back, already at first server" ) ); | ||
| butAccessiblePrevious->setEnabled ( false ); | ||
| } | ||
|
|
||
| // Update "Next" button with next server name | ||
| if ( currentIndex < lvwServers->topLevelItemCount() - 1 ) | ||
| { | ||
| QTreeWidgetItem* nextItem = lvwServers->topLevelItem ( currentIndex + 1 ); | ||
| QString nextName = nextItem->text ( LVC_NAME ); | ||
| butAccessibleNext->setText ( nextName + u8" \u2192" ); | ||
| butAccessibleNext->setAccessibleName ( tr ( "Next server: %1" ).arg ( nextName ) ); | ||
| butAccessibleNext->setAccessibleDescription ( tr ( "Go to next server: %1" ).arg ( nextName ) ); | ||
| butAccessibleNext->setEnabled ( true ); | ||
| } | ||
| else | ||
| { | ||
| butAccessibleNext->setText ( tr ( "(last)" ) + u8" \u2192" ); | ||
| butAccessibleNext->setAccessibleName ( tr ( "No next server - at last server" ) ); | ||
| butAccessibleNext->setAccessibleDescription ( tr ( "Cannot go forward, already at last server" ) ); | ||
| butAccessibleNext->setEnabled ( false ); | ||
| } | ||
|
|
||
| // Force VoiceOver to announce the change | ||
| QAccessible::updateAccessibility ( new QAccessibleValueChangeEvent ( lblAccessibleServerInfo, accessibleText ) ); | ||
| } | ||
| else | ||
| { | ||
| // Reset navigation buttons | ||
| butAccessiblePrevious->setText ( u8"\u2190 " + tr ( "Previous" ) ); | ||
| butAccessiblePrevious->setAccessibleName ( tr ( "Navigate to previous server" ) ); | ||
| butAccessiblePrevious->setEnabled ( lvwServers->topLevelItemCount() > 0 ); | ||
|
|
||
| butAccessibleNext->setText ( tr ( "Next" ) + u8" \u2192" ); | ||
| butAccessibleNext->setAccessibleName ( tr ( "Navigate to next server" ) ); | ||
| butAccessibleNext->setEnabled ( lvwServers->topLevelItemCount() > 0 ); | ||
|
|
||
| // No server selected or musician child selected | ||
| lblAccessibleServerInfo->setText ( tr ( "<i>No server selected or in musician selection</i>" ) ); | ||
| lblAccessibleServerInfo->setAccessibleName ( tr ( "No server selected or in musician selection" ) ); | ||
| lblAccessibleServerInfo->setAccessibleDescription ( tr ( "No server selected. Use Previous/Next buttons or Alt+Up/Down to navigate servers." ) ); | ||
|
|
||
| } | ||
| } | ||
|
|
||
| void CConnectDlg::OnAccessiblePreviousClicked() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So these will trigger
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think they should skip Clients -- it needs to step through all entries in the list, otherwise the list isn't fully accessible. |
||
| { | ||
| // Navigate to previous server | ||
| QList<QTreeWidgetItem*> selectedItems = lvwServers->selectedItems(); | ||
| QTreeWidgetItem* currentItem = selectedItems.isEmpty() ? nullptr : selectedItems.first(); | ||
|
|
||
| // Get current item or first item if none selected | ||
| if ( currentItem == nullptr ) | ||
| { | ||
| // Select first item | ||
| if ( lvwServers->topLevelItemCount() > 0 ) | ||
| { | ||
| lvwServers->setCurrentItem ( lvwServers->topLevelItem ( 0 ) ); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| // If current item is a musician (child), move to parent | ||
| if ( currentItem->parent() != nullptr ) | ||
| { | ||
| lvwServers->setCurrentItem ( currentItem->parent() ); | ||
| return; | ||
| } | ||
|
|
||
| // Get previous server item | ||
| int currentIndex = lvwServers->indexOfTopLevelItem ( currentItem ); | ||
| if ( currentIndex > 0 ) | ||
| { | ||
| lvwServers->setCurrentItem ( lvwServers->topLevelItem ( currentIndex - 1 ) ); | ||
| } | ||
| } | ||
|
|
||
| void CConnectDlg::OnAccessibleNextClicked() | ||
| { | ||
| // Navigate to next server | ||
| QList<QTreeWidgetItem*> selectedItems = lvwServers->selectedItems(); | ||
| QTreeWidgetItem* currentItem = selectedItems.isEmpty() ? nullptr : selectedItems.first(); | ||
|
|
||
| // Get current item or first item if none selected | ||
| if ( currentItem == nullptr ) | ||
| { | ||
| // Select first item | ||
| if ( lvwServers->topLevelItemCount() > 0 ) | ||
| { | ||
| lvwServers->setCurrentItem ( lvwServers->topLevelItem ( 0 ) ); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| // If current item is a musician (child), find next server | ||
| if ( currentItem->parent() != nullptr ) | ||
| { | ||
| currentItem = currentItem->parent(); | ||
| } | ||
|
|
||
| // Find next server item (skip musician children) | ||
| int currentIndex = lvwServers->indexOfTopLevelItem ( currentItem ); | ||
| if ( currentIndex >= 0 && currentIndex < lvwServers->topLevelItemCount() - 1 ) | ||
| { | ||
| lvwServers->setCurrentItem ( lvwServers->topLevelItem ( currentIndex + 1 ) ); | ||
| } | ||
| } | ||
|
|
||
| void CConnectDlg::OnToggleAccessibleClicked() | ||
| { | ||
| bool isVisible = wAccessibleNavPanel->isVisible(); | ||
| wAccessibleNavPanel->setVisible ( !isVisible ); | ||
|
|
||
| if ( isVisible ) | ||
| { | ||
| butToggleAccessible->setText ( u8"\u25B6 " + tr ( "Show Accessible Controls" ) ); | ||
| butToggleAccessible->setAccessibleDescription ( tr ( "Show the accessible navigation controls" ) ); | ||
| } | ||
| else | ||
| { | ||
| butToggleAccessible->setText ( u8"\u25BC " + tr ( "Hide Accessible Controls" ) ); | ||
| butToggleAccessible->setAccessibleDescription ( tr ( "Hide the accessible navigation controls" ) ); | ||
| } | ||
| } | ||
| #endif | ||
|
|
||
| void CConnectDlg::OnCustomDirectoriesChanged() | ||
| { | ||
|
|
||
|
|
||




There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this will be an additional build. Will it be included in the macOS bundle so it all gets signed once in the Apple Store?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, does this need to be a compile time option? Just have the accessibility panel hidden unless turned on in Settings->User Profile->User Interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd need to track a change in the setting, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. I was also not sure if it should or should not be. Best case, it's just a button/checkbox.