Skip to content

Commit 283161c

Browse files
feat(accessibility): add screen reader support for country select box
1 parent b4f7c0f commit 283161c

File tree

2 files changed

+166
-0
lines changed

2 files changed

+166
-0
lines changed

Telegram/Resources/langs/lang.strings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7742,5 +7742,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
77427742
"lng_sr_bot_verified_badge" = "Verified Bot";
77437743
"lng_sr_profile_menu" = "Profile menu";
77447744
"lng_sr_close_panel" = "Close panel";
7745+
"lng_sr_country_column_name" = "Country name";
77457746

77467747
// Keys finished

Telegram/SourceFiles/ui/boxes/country_select_box.cpp

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,22 @@ For license and copyright information please follow this link:
88
#include "ui/boxes/country_select_box.h"
99

1010
#include "lang/lang_keys.h"
11+
#include "base/screen_reader_state.h"
12+
#include "ui/accessible/ui_accessible_item.h"
1113
#include "ui/widgets/scroll_area.h"
1214
#include "ui/widgets/multi_select.h"
1315
#include "ui/effects/ripple_animation.h"
1416
#include "ui/painter.h"
17+
#include "base/invoke_queued.h"
1518
#include "countries/countries_instance.h"
1619
#include "styles/style_layers.h"
1720
#include "styles/style_boxes.h"
1821
#include "styles/style_intro.h"
1922

23+
#include <unordered_map>
24+
2025
#include <QtCore/QRegularExpression>
26+
#include <QtWidgets/QApplication>
2127

2228
namespace Ui {
2329
namespace {
@@ -48,8 +54,87 @@ class CountrySelectBox::Inner : public RpWidget {
4854
return _mustScrollTo.events();
4955
}
5056

57+
QAccessible::Role accessibilityRole() override {
58+
return QAccessible::List;
59+
}
60+
61+
QAccessible::Role accessibilityChildRole() const override {
62+
return QAccessible::ListItem;
63+
}
64+
65+
QAccessible::State accessibilityChildState(int index) const override {
66+
QAccessible::State state;
67+
state.selectable = true;
68+
if (base::ScreenReaderState::Instance()->active()) {
69+
state.focusable = true;
70+
}
71+
if (index == _selected) {
72+
state.selected = true;
73+
state.active = true;
74+
if (hasFocus()) {
75+
state.focused = true;
76+
}
77+
}
78+
return state;
79+
}
80+
81+
int accessibilityChildCount() const override {
82+
return int(current().size());
83+
}
84+
85+
QString accessibilityChildName(int index) const override {
86+
const auto &list = current();
87+
if (index < 0 || index >= int(list.size())) {
88+
return {};
89+
}
90+
if (_type == Type::Phones) {
91+
return list[index].country + u", +"_q + list[index].code;
92+
}
93+
return list[index].country;
94+
}
95+
96+
QRect accessibilityChildRect(int index) const override {
97+
const auto &list = current();
98+
if (index < 0 || index >= int(list.size())) {
99+
return QRect();
100+
}
101+
return QRect(0, st::countriesSkip + index * _rowHeight, width(), _rowHeight);
102+
}
103+
104+
int accessibilityChildColumnCount(int row) const override {
105+
return (_type == Type::Phones) ? 2 : 1;
106+
}
107+
108+
QAccessible::Role accessibilityChildSubItemRole() const override {
109+
return QAccessible::Cell;
110+
}
111+
112+
QString accessibilityChildSubItemName(int row, int column) const override {
113+
if (column == 0) {
114+
return tr::lng_sr_country_column_name(tr::now);
115+
} else if (column == 1 && _type == Type::Phones) {
116+
return tr::lng_country_code(tr::now);
117+
}
118+
return {};
119+
}
120+
121+
QString accessibilityChildSubItemValue(int row, int column) const override {
122+
const auto &list = current();
123+
if (row < 0 || row >= int(list.size())) {
124+
return {};
125+
}
126+
if (column == 0) {
127+
return list[row].country;
128+
} else if (column == 1 && _type == Type::Phones) {
129+
return u"+"_q + list[row].code;
130+
}
131+
return {};
132+
}
133+
51134
protected:
135+
void focusInEvent(QFocusEvent *e) override;
52136
void paintEvent(QPaintEvent *e) override;
137+
void keyPressEvent(QKeyEvent *e) override;
53138
void enterEventHook(QEnterEvent *e) override;
54139
void leaveEventHook(QEvent *e) override;
55140
void mouseMoveEvent(QMouseEvent *e) override;
@@ -203,6 +288,13 @@ CountrySelectBox::Inner::Inner(
203288
_filter = u"a"_q;
204289
updateFilter(filter);
205290
}, lifetime());
291+
292+
setAccessibleName(tr::lng_country_select(tr::now));
293+
294+
base::ScreenReaderState::Instance()->activeValue(
295+
) | rpl::on_next([=](bool active) {
296+
setFocusPolicy(active ? Qt::TabFocus : Qt::NoFocus);
297+
}, lifetime());
206298
}
207299

208300
void CountrySelectBox::Inner::init() {
@@ -262,6 +354,27 @@ void CountrySelectBox::Inner::init() {
262354
}
263355
}
264356

357+
void CountrySelectBox::Inner::focusInEvent(QFocusEvent *e) {
358+
// Select first item when focus enters.
359+
const auto &list = current();
360+
if (_selected < 0 && !list.empty()) {
361+
_selected = 0;
362+
updateSelectedRow();
363+
}
364+
365+
RpWidget::focusInEvent(e);
366+
367+
if (_selected >= 0 && base::ScreenReaderState::Instance()->active()) {
368+
const auto index = _selected;
369+
InvokeQueued(this, [=] {
370+
if (_selected != index || !hasFocus()) {
371+
return;
372+
}
373+
accessibilityChildFocused(index);
374+
});
375+
}
376+
}
377+
265378
void CountrySelectBox::Inner::paintEvent(QPaintEvent *e) {
266379
Painter p(this);
267380
QRect r(e->rect());
@@ -316,6 +429,50 @@ void CountrySelectBox::Inner::paintEvent(QPaintEvent *e) {
316429
}
317430
}
318431

432+
void CountrySelectBox::Inner::keyPressEvent(QKeyEvent *e) {
433+
if (e->key() == Qt::Key_Down) {
434+
selectSkip(1);
435+
} else if (e->key() == Qt::Key_Up) {
436+
selectSkip(-1);
437+
} else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_PageUp) {
438+
const auto visibleHeight = visibleRegion().boundingRect().height();
439+
const auto rowsPerPage = std::max(visibleHeight / _rowHeight, 1);
440+
selectSkip(e->key() == Qt::Key_PageDown ? rowsPerPage : -rowsPerPage);
441+
} else if (e->key() == Qt::Key_Home) {
442+
const auto &list = current();
443+
if (!list.empty()) {
444+
_selected = 0;
445+
_mustScrollTo.fire(ScrollToRequest(
446+
st::countriesSkip,
447+
st::countriesSkip + _rowHeight));
448+
update();
449+
if (base::ScreenReaderState::Instance()->active()) {
450+
accessibilityChildNameChanged(_selected);
451+
accessibilityChildFocused(_selected);
452+
}
453+
}
454+
} else if (e->key() == Qt::Key_End) {
455+
const auto &list = current();
456+
if (!list.empty()) {
457+
_selected = int(list.size()) - 1;
458+
_mustScrollTo.fire(ScrollToRequest(
459+
st::countriesSkip + _selected * _rowHeight,
460+
st::countriesSkip + (_selected + 1) * _rowHeight));
461+
update();
462+
if (base::ScreenReaderState::Instance()->active()) {
463+
accessibilityChildNameChanged(_selected);
464+
accessibilityChildFocused(_selected);
465+
}
466+
}
467+
} else if (!e->isAutoRepeat()
468+
&& (e->key() == Qt::Key_Return
469+
|| e->key() == Qt::Key_Enter)) {
470+
chooseCountry();
471+
} else {
472+
RpWidget::keyPressEvent(e);
473+
}
474+
}
475+
319476
void CountrySelectBox::Inner::enterEventHook(QEnterEvent *e) {
320477
setMouseTracking(true);
321478
}
@@ -426,6 +583,10 @@ void CountrySelectBox::Inner::selectSkip(int32 dir) {
426583
st::countriesSkip + (_selected + 1) * _rowHeight));
427584
}
428585
update();
586+
if (_selected >= 0 && base::ScreenReaderState::Instance()->active()) {
587+
accessibilityChildNameChanged(_selected);
588+
accessibilityChildFocused(_selected);
589+
}
429590
}
430591

431592
void CountrySelectBox::Inner::selectSkipPage(int32 h, int32 dir) {
@@ -457,6 +618,10 @@ void CountrySelectBox::Inner::updateSelected(QPoint localPos) {
457618
updateSelectedRow();
458619
_selected = selected;
459620
updateSelectedRow();
621+
if (_selected >= 0 && base::ScreenReaderState::Instance()->active()) {
622+
accessibilityChildNameChanged(_selected);
623+
accessibilityChildFocused(_selected);
624+
}
460625
}
461626
}
462627

0 commit comments

Comments
 (0)