-
Notifications
You must be signed in to change notification settings - Fork 244
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
Changes from 8 commits
d148bc1
46c45a3
867e87c
6101375
e6d543a
1d39c1d
2ba71f7
0be5b53
8f7929f
0247b60
4a08bfd
6f43c78
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 | ||
|
ann0see marked this conversation as resolved.
|
||
|
|
||
| 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 ); | ||
|
ann0see marked this conversation as resolved.
Outdated
|
||
| 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 | ||
|
ann0see marked this conversation as resolved.
Outdated
|
||
| // 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 ); | ||
|
ann0see marked this conversation as resolved.
|
||
| 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.