@@ -9,6 +9,8 @@ For license and copyright information please follow this link:
99
1010#include " data/data_peer_values.h"
1111#include " lang/lang_keys.h"
12+ #include " base/screen_reader_state.h"
13+ #include " ui/accessible/ui_accessible_item.h"
1214#include " ui/boxes/choose_language_box.h"
1315#include " ui/widgets/checkbox.h"
1416#include " ui/widgets/buttons.h"
@@ -51,6 +53,8 @@ For license and copyright information please follow this link:
5153#include " styles/style_menu_icons.h"
5254#include " styles/style_settings.h"
5355
56+ #include < unordered_map>
57+
5458#include < QtGui/QGuiApplication>
5559#include < QtGui/QClipboard>
5660
@@ -71,22 +75,101 @@ class Rows : public Ui::RpWidget {
7175
7276 int count () const ;
7377 int selected () const ;
78+ int chosenIndex () const ;
7479 void setSelected (int selected);
7580 rpl::producer<bool > hasSelection () const ;
7681 rpl::producer<bool > isEmpty () const ;
7782
7883 void activateSelected ();
84+ void selectSkip (int dir);
7985 rpl::producer<Language> activations () const ;
8086 void changeChosen (const QString &chosen);
8187
8288 Ui::ScrollToRequest rowScrollRequest (int index) const ;
8389
8490 static int DefaultRowHeight ();
8591
92+ QAccessible::Role accessibilityRole () override {
93+ return QAccessible::List;
94+ }
95+
96+ QAccessible::Role accessibilityChildRole () const override {
97+ return QAccessible::RadioButton;
98+ }
99+
100+ QAccessible::State accessibilityChildState (int index) const override {
101+ QAccessible::State state;
102+ if (base::ScreenReaderState::Instance ()->active ()) {
103+ state.focusable = true ;
104+ }
105+ state.checkable = true ;
106+ state.checked = (index == chosenIndex ());
107+ if (index == selected ()) {
108+ state.active = true ;
109+ if (hasFocus ()) {
110+ state.focused = true ;
111+ }
112+ }
113+ return state;
114+ }
115+
116+ int accessibilityChildCount () const override {
117+ return count ();
118+ }
119+
120+ QString accessibilityChildName (int index) const override {
121+ if (index < 0 || index >= count ()) {
122+ return {};
123+ }
124+ const auto &row = rowByIndex (index);
125+ // Announce native name followed by English name.
126+ return row.data .nativeName + u" , " _q + row.data .name ;
127+ }
128+
129+ QRect accessibilityChildRect (int index) const override {
130+ if (index < 0 || index >= count ()) {
131+ return QRect ();
132+ }
133+ const auto &row = rowByIndex (index);
134+ return QRect (0 , row.top , width (), row.height );
135+ }
136+
137+ int accessibilityChildColumnCount (int row) const override {
138+ return 2 ;
139+ }
140+
141+ QAccessible::Role accessibilityChildSubItemRole () const override {
142+ return QAccessible::Cell;
143+ }
144+
145+ QString accessibilityChildSubItemName (int row, int column) const override {
146+ if (column == 0 ) {
147+ return tr::lng_sr_languages_column_native (tr::now);
148+ } else if (column == 1 ) {
149+ return tr::lng_sr_languages_column_name (tr::now);
150+ }
151+ return {};
152+ }
153+
154+ QString accessibilityChildSubItemValue (int row, int column) const override {
155+ if (row < 0 || row >= count ()) {
156+ return {};
157+ }
158+ const auto &data = rowByIndex (row).data ;
159+ if (column == 0 ) {
160+ return data.nativeName ;
161+ } else if (column == 1 ) {
162+ return data.name ;
163+ }
164+ return {};
165+ }
166+
86167protected:
87168 int resizeGetHeight (int newWidth) override ;
88169
170+ void focusInEvent (QFocusEvent *e) override ;
89171 void paintEvent (QPaintEvent *e) override ;
172+ void keyPressEvent (QKeyEvent *e) override ;
90173 void mouseMoveEvent (QMouseEvent *e) override ;
91174 void mousePressEvent (QMouseEvent *e) override ;
92175 void mouseReleaseEvent (QMouseEvent *e) override ;
@@ -288,6 +371,63 @@ Rows::Rows(
288371 resizeToWidth (width ());
289372 setAttribute (Qt::WA_MouseTracking);
290373 update ();
374+
375+ setAccessibleName (tr::lng_languages (tr::now));
376+
377+ base::ScreenReaderState::Instance ()->activeValue (
378+ ) | rpl::on_next ([=](bool active) {
379+ setFocusPolicy (active ? Qt::TabFocus : Qt::NoFocus);
380+ }, lifetime ());
381+ }
382+
383+ void Rows::focusInEvent (QFocusEvent *e) {
384+ // Select first item or chosen item when focus enters.
385+ if (selected () < 0 && count () > 0 ) {
386+ const auto chosen = chosenIndex ();
387+ setSelected (chosen >= 0 ? chosen : 0 );
388+ }
389+
390+ RpWidget::focusInEvent (e);
391+
392+ if (base::ScreenReaderState::Instance ()->active ()) {
393+ const auto index = selected ();
394+ if (index >= 0 ) {
395+ InvokeQueued (this , [=] {
396+ if (selected () != index || !hasFocus ()) {
397+ return ;
398+ }
399+ accessibilityChildFocused (index);
400+ });
401+ }
402+ }
403+ }
404+
405+ void Rows::keyPressEvent (QKeyEvent *e) {
406+ const auto key = e->key ();
407+ if (key == Qt::Key_Down) {
408+ selectSkip (1 );
409+ } else if (key == Qt::Key_Up) {
410+ selectSkip (-1 );
411+ } else if (key == Qt::Key_PageDown || key == Qt::Key_PageUp) {
412+ const auto visibleHeight = visibleRegion ().boundingRect ().height ();
413+ const auto rowsPerPage = std::max (visibleHeight / DefaultRowHeight (), 1 );
414+ selectSkip (key == Qt::Key_PageDown ? rowsPerPage : -rowsPerPage);
415+ } else if (key == Qt::Key_Home) {
416+ if (count () > 0 ) {
417+ setSelected (0 );
418+ }
419+ } else if (key == Qt::Key_End) {
420+ if (count () > 0 ) {
421+ setSelected (count () - 1 );
422+ }
423+ } else if (!e->isAutoRepeat ()
424+ && (key == Qt::Key_Space
425+ || key == Qt::Key_Return
426+ || key == Qt::Key_Enter)) {
427+ activateSelected ();
428+ } else {
429+ RpWidget::keyPressEvent (e);
430+ }
291431}
292432
293433void Rows::mouseMoveEvent (QMouseEvent *e) {
@@ -555,7 +695,12 @@ void Rows::setForceRippled(not_null<Row*> row, bool rippled) {
555695}
556696
557697void Rows::activateByIndex (int index) {
698+ _chosen = rowByIndex (index).data .id ;
558699 _activations.fire_copy (rowByIndex (index).data );
700+ if (base::ScreenReaderState::Instance ()->active ()) {
701+ accessibilityChildStateChanged (index, { .checked = true });
702+ accessibilityChildNameChanged (index);
703+ }
559704}
560705
561706void Rows::leaveEventHook (QEvent *e) {
@@ -631,21 +776,60 @@ int Rows::selected() const {
631776 return indexFromSelection (_selected);
632777}
633778
779+ int Rows::chosenIndex () const {
780+ for (auto i = 0 , n = count (); i < n; ++i) {
781+ if (rowByIndex (i).data .id == _chosen) {
782+ return i;
783+ }
784+ }
785+ return -1 ;
786+ }
787+
634788void Rows::activateSelected () {
635789 const auto index = selected ();
636790 if (index >= 0 ) {
637791 activateByIndex (index);
638792 }
639793}
640794
795+ void Rows::selectSkip (int dir) {
796+ const auto limit = count ();
797+ auto now = selected ();
798+ // If no keyboard selection, start from the checked item.
799+ if (now < 0 ) {
800+ now = chosenIndex ();
801+ }
802+ if (now >= 0 ) {
803+ const auto changed = now + dir;
804+ if (changed < 0 ) {
805+ setSelected (0 );
806+ } else if (changed >= limit) {
807+ setSelected (limit - 1 );
808+ } else {
809+ setSelected (changed);
810+ }
811+ } else if (dir > 0 ) {
812+ setSelected (0 );
813+ }
814+ }
815+
641816rpl::producer<Language> Rows::activations () const {
642817 return _activations.events ();
643818}
644819
645820void Rows::changeChosen (const QString &chosen) {
821+ const auto oldIndex = chosenIndex ();
822+ _chosen = chosen;
646823 for (const auto &row : _rows) {
647824 row.check ->setChecked (row.data .id == chosen, anim::type::normal);
648825 }
826+ if (base::ScreenReaderState::Instance ()->active ()) {
827+ const auto newIndex = chosenIndex ();
828+ if (newIndex != oldIndex && newIndex >= 0 ) {
829+ accessibilityChildStateChanged (newIndex, { .checked = true });
830+ accessibilityChildNameChanged (newIndex);
831+ }
832+ }
649833}
650834
651835void Rows::setSelected (int selected) {
@@ -656,6 +840,11 @@ void Rows::setSelected(int selected) {
656840 } else {
657841 updateSelected ({});
658842 }
843+ if (selected >= 0 && selected < limit
844+ && base::ScreenReaderState::Instance ()->active ()) {
845+ accessibilityChildNameChanged (selected);
846+ accessibilityChildFocused (selected);
847+ }
659848}
660849
661850rpl::producer<bool > Rows::hasSelection () const {
0 commit comments