Skip to content

Commit d148bc1

Browse files
committed
Try to fix QTreeWidget accessibility bug
Co-authored-by: GitHub Copilot
1 parent cb5a880 commit d148bc1

2 files changed

Lines changed: 81 additions & 5 deletions

File tree

src/connectdlg.cpp

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,22 +74,22 @@ static QString mapVersionStr ( const QString& versionStr )
7474
// Subclass of QTreeWidgetItem that allows LVC_VERSION to sort by the UserRole data value
7575
CMappedTreeWidgetItem::CMappedTreeWidgetItem ( QTreeWidget* owner ) : QTreeWidgetItem ( owner ), owner ( owner ) {}
7676

77-
bool CMappedTreeWidgetItem::operator<( const QTreeWidgetItem& other ) const
77+
bool CMappedTreeWidgetItem::operator< ( const QTreeWidgetItem& other ) const
7878
{
7979
if ( !owner )
80-
return QTreeWidgetItem::operator<( other );
80+
return QTreeWidgetItem::operator< ( other );
8181

8282
int column = owner->sortColumn();
8383

8484
// we only need this override for comparing server versions
8585
if ( column != CConnectDlg::LVC_VERSION )
86-
return QTreeWidgetItem::operator<( other );
86+
return QTreeWidgetItem::operator< ( other );
8787

8888
QVariant lhs = data ( column, Qt::UserRole );
8989
QVariant rhs = other.data ( column, Qt::UserRole );
9090

9191
if ( !lhs.isValid() || !rhs.isValid() )
92-
return QTreeWidgetItem::operator<( other );
92+
return QTreeWidgetItem::operator< ( other );
9393

9494
return lhs.toString() < rhs.toString();
9595
}
@@ -243,6 +243,9 @@ CConnectDlg::CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteR
243243
// to get default return key behaviour working
244244
QObject::connect ( lvwServers, &QTreeWidget::activated, this, &CConnectDlg::OnConnectClicked );
245245

246+
// connect selection change for accessibility support
247+
QObject::connect ( lvwServers, &QTreeWidget::itemSelectionChanged, this, &CConnectDlg::OnServerListItemSelectionChanged );
248+
246249
// line edit
247250
QObject::connect ( edtFilter, &QLineEdit::textEdited, this, &CConnectDlg::OnFilterTextEdited );
248251

@@ -631,6 +634,75 @@ void CConnectDlg::OnServerAddrEditTextChanged ( const QString& )
631634
lvwServers->clearSelection();
632635
}
633636

637+
QString CConnectDlg::BuildAccessibleTextForItem ( const QTreeWidgetItem* pItem ) const
638+
{
639+
// Return early if item is null to avoid crashes
640+
if ( !pItem )
641+
{
642+
return QString();
643+
}
644+
645+
// Check if this is a child item (musician) or parent item (server).
646+
// Child items have a parent and represent individual musicians connected to a server.
647+
// Parent items represent servers and contain multiple columns with server information.
648+
if ( pItem->parent() != nullptr )
649+
{
650+
// This is a musician/child item - announce the musician name
651+
return tr ( "Musician: %1" ).arg ( pItem->text ( LVC_NAME ) );
652+
}
653+
654+
// This is a server item - build accessible text with all available information.
655+
// Start with the server name, then append other columns that have data.
656+
QString accessibleText = tr ( "Server: %1" ).arg ( pItem->text ( LVC_NAME ) );
657+
658+
// Helper lambda to append column data if it exists
659+
auto appendColumnIfNotEmpty = [&] ( int column, const QString& label ) {
660+
QString value = pItem->text ( column );
661+
if ( !value.isEmpty() )
662+
{
663+
accessibleText += tr ( ", %1: %2" ).arg ( label ).arg ( value );
664+
}
665+
};
666+
667+
// Append all server information columns in the order they appear in the UI
668+
appendColumnIfNotEmpty ( LVC_PING, tr ( "Ping" ) );
669+
appendColumnIfNotEmpty ( LVC_CLIENTS, tr ( "Musicians" ) );
670+
appendColumnIfNotEmpty ( LVC_LOCATION, tr ( "Location" ) );
671+
appendColumnIfNotEmpty ( LVC_VERSION, tr ( "Version" ) );
672+
673+
return accessibleText;
674+
}
675+
676+
void CConnectDlg::UpdateAccessibilityForItem ( QTreeWidgetItem* pItem )
677+
{
678+
// Update the accessible description and notify the accessibility system
679+
// when items are selected. This improves screen reader support on all platforms,
680+
// including VoiceOver on macOS, NVDA/JAWS on Windows, and Orca on Linux.
681+
if ( !pItem )
682+
{
683+
return;
684+
}
685+
686+
QString accessibleText = BuildAccessibleTextForItem ( pItem );
687+
lvwServers->setAccessibleDescription ( accessibleText );
688+
QAccessible::updateAccessibility ( new QAccessibleValueChangeEvent ( lvwServers, accessibleText ) );
689+
}
690+
691+
void CConnectDlg::OnServerListItemSelectionChanged()
692+
{
693+
QList<QTreeWidgetItem*> selectedItems = lvwServers->selectedItems();
694+
695+
if ( !selectedItems.isEmpty() )
696+
{
697+
UpdateAccessibilityForItem ( selectedItems.first() );
698+
}
699+
else
700+
{
701+
// Clear accessible description when nothing is selected
702+
lvwServers->setAccessibleDescription ( tr ( "Server list" ) );
703+
}
704+
}
705+
634706
void CConnectDlg::OnCustomDirectoriesChanged()
635707
{
636708

src/connectdlg.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <QLocale>
3333
#include <QtConcurrent>
3434
#include <QRegularExpression>
35+
#include <QAccessible>
3536
#include "global.h"
3637
#include "util.h"
3738
#include "settings.h"
@@ -51,7 +52,7 @@ class CMappedTreeWidgetItem : public QTreeWidgetItem
5152
public:
5253
explicit CMappedTreeWidgetItem ( QTreeWidget* owner = nullptr );
5354

54-
bool operator<( const QTreeWidgetItem& other ) const override;
55+
bool operator< ( const QTreeWidgetItem& other ) const override;
5556

5657
private:
5758
QTreeWidget* owner = nullptr;
@@ -104,6 +105,8 @@ class CConnectDlg : public CBaseDlg, private Ui_CConnectDlgBase
104105
void RequestServerList();
105106
void EmitCLServerListPingMes ( const CHostAddress& haServerAddress, const bool bNeedVersion );
106107
void UpdateDirectoryComboBox();
108+
QString BuildAccessibleTextForItem ( const QTreeWidgetItem* pItem ) const;
109+
void UpdateAccessibilityForItem ( QTreeWidgetItem* pItem );
107110

108111
CClientSettings* pSettings;
109112

@@ -132,6 +135,7 @@ public slots:
132135
void OnDeleteServerAddrClicked();
133136
void OnTimerPing();
134137
void OnTimerReRequestServList();
138+
void OnServerListItemSelectionChanged();
135139

136140
signals:
137141
void ReqServerListQuery ( CHostAddress InetAddr );

0 commit comments

Comments
 (0)