diff --git a/src/connectdlg.cpp b/src/connectdlg.cpp index 1c4404ea2b..4828f1b825 100644 --- a/src/connectdlg.cpp +++ b/src/connectdlg.cpp @@ -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 + + 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 ); + butAccessiblePrevious->setObjectName ( "butAccessiblePrevious" ); + butAccessiblePrevious->setAccessibleName ( tr ( "Go to previous server" ) ); + butAccessiblePrevious->setAccessibleDescription ( tr ( "Navigate to the previous server in the list" ) ); + 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 ( 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 ); +#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 + // 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() +{ + QList selectedItems = lvwServers->selectedItems(); + + if ( !selectedItems.isEmpty() && selectedItems.first()->parent() == nullptr ) + { + // 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 ( "No server selected or in musician selection" ) ); + 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() +{ + // Navigate to previous server + QList 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 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() { diff --git a/src/connectdlg.h b/src/connectdlg.h index 1a3b00abb7..3e0d483b7b 100644 --- a/src/connectdlg.h +++ b/src/connectdlg.h @@ -24,6 +24,11 @@ #pragma once +// Enable accessible server list by default (can be disabled with -DDISABLE_ACCESSIBLE_SERVER_LIST) +#ifndef DISABLE_ACCESSIBLE_SERVER_LIST +# define USE_ACCESSIBLE_SERVER_LIST +#endif + #include #include #include @@ -32,6 +37,9 @@ #include #include #include +#include +#include +#include #include "global.h" #include "util.h" #include "settings.h" @@ -105,8 +113,21 @@ class CConnectDlg : public CBaseDlg, private Ui_CConnectDlgBase void EmitCLServerListPingMes ( const CHostAddress& haServerAddress, const bool bNeedVersion ); void UpdateDirectoryComboBox(); +#ifdef USE_ACCESSIBLE_SERVER_LIST + void UpdateAccessibleServerInfo(); +#endif + CClientSettings* pSettings; +#ifdef USE_ACCESSIBLE_SERVER_LIST + // Accessible navigation panel for screen readers + QWidget* wAccessibleNavPanel; // Container for accessible navigation + QLabel* lblAccessibleServerInfo; // Label showing current server information (read-only) + QPushButton* butAccessiblePrevious; // Button to navigate to previous server + QPushButton* butAccessibleNext; // Button to navigate to next server + QPushButton* butToggleAccessible; // Button to show/hide accessible panel +#endif + QTimer TimerPing; QTimer TimerReRequestServList; QTimer TimerInitialSort; @@ -132,6 +153,13 @@ public slots: void OnDeleteServerAddrClicked(); void OnTimerPing(); void OnTimerReRequestServList(); + void OnServerListItemSelectionChanged(); + +#ifdef USE_ACCESSIBLE_SERVER_LIST + void OnAccessiblePreviousClicked(); + void OnAccessibleNextClicked(); + void OnToggleAccessibleClicked(); +#endif signals: void ReqServerListQuery ( CHostAddress InetAddr );