Improve accessibility code for countries list.

This commit is contained in:
John Preston
2026-04-20 22:06:37 +07:00
parent c0bcdc3829
commit fdc0f1e648
+190 -163
View File
@@ -7,56 +7,55 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/language_box.h"
#include "data/data_peer_values.h"
#include "lang/lang_keys.h"
#include "base/screen_reader_state.h"
#include "ui/accessible/ui_accessible_item.h"
#include "ui/boxes/choose_language_box.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/text/text_entity.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/effects/ripple_animation.h"
#include "ui/toast/toast.h"
#include "ui/text/text_options.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "storage/localstorage.h"
#include "base/platform/base_platform_info.h"
#include "boxes/abstract_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/translate_box.h"
#include "ui/boxes/confirm_box.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "core/application.h"
#include "base/platform/base_platform_info.h"
#include "lang/lang_instance.h"
#include "data/data_peer_values.h"
#include "lang/lang_cloud_manager.h"
#include "lang/lang_instance.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "platform/platform_translate_provider.h"
#include "settings/settings_common.h"
#include "spellcheck/spellcheck_types.h"
#include "storage/localstorage.h"
#include "ui/accessible/ui_accessible_item.h"
#include "ui/boxes/choose_language_box.h"
#include "ui/boxes/confirm_box.h"
#include "ui/effects/ripple_animation.h"
#include "ui/text/text_entity.h"
#include "ui/text/text_options.h"
#include "ui/toast/toast.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/dropdown_menu.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/multi_select.h"
#include "ui/widgets/scroll_area.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/painter.h"
#include "ui/screen_reader_mode.h"
#include "ui/ui_utility.h"
#include "ui/vertical_list.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "mainwidget.h"
#include "mainwindow.h"
#include "styles/style_boxes.h"
#include "styles/style_info.h"
#include "styles/style_passport.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_passport.h"
#include "styles/style_settings.h"
#include <unordered_map>
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
#include <QtGui/QGuiApplication>
namespace {
@@ -89,80 +88,17 @@ public:
static int DefaultRowHeight();
QAccessible::Role accessibilityRole() override {
return QAccessible::List;
}
QAccessible::Role accessibilityChildRole() const override {
return QAccessible::RadioButton;
}
QAccessible::State accessibilityChildState(int index) const override {
QAccessible::State state;
if (base::ScreenReaderState::Instance()->active()) {
state.focusable = true;
}
state.checkable = true;
state.checked = (index == chosenIndex());
if (index == selected()) {
state.active = true;
if (hasFocus()) {
state.focused = true;
}
}
return state;
}
int accessibilityChildCount() const override {
return count();
}
QString accessibilityChildName(int index) const override {
if (index < 0 || index >= count()) {
return {};
}
const auto &row = rowByIndex(index);
// Announce native name followed by English name.
return row.data.nativeName + u", "_q + row.data.name;
}
QRect accessibilityChildRect(int index) const override {
if (index < 0 || index >= count()) {
return QRect();
}
const auto &row = rowByIndex(index);
return QRect(0, row.top, width(), row.height);
}
int accessibilityChildColumnCount(int row) const override {
return 2;
}
QAccessible::Role accessibilityChildSubItemRole() const override {
return QAccessible::Cell;
}
QString accessibilityChildSubItemName(int row, int column) const override {
if (column == 0) {
return tr::lng_sr_languages_column_native(tr::now);
} else if (column == 1) {
return tr::lng_sr_languages_column_name(tr::now);
}
return {};
}
QString accessibilityChildSubItemValue(int row, int column) const override {
if (row < 0 || row >= count()) {
return {};
}
const auto &data = rowByIndex(row).data;
if (column == 0) {
return data.nativeName;
} else if (column == 1) {
return data.name;
}
return {};
}
QAccessible::Role accessibilityRole() override;
Qt::FocusPolicy accessibilityFocusPolicy() override;
QAccessible::Role accessibilityChildRole() const override;
QAccessible::State accessibilityChildState(int index) const override;
int accessibilityChildCount() const override;
QString accessibilityChildName(int index) const override;
QRect accessibilityChildRect(int index) const override;
int accessibilityChildColumnCount(int row) const override;
QAccessible::Role accessibilityChildSubItemRole() const override;
QString accessibilityChildSubItemName(int row, int column) const override;
QString accessibilityChildSubItemValue(int row, int column) const override;
protected:
int resizeGetHeight(int newWidth) override;
@@ -237,6 +173,13 @@ private:
void repaintChecked(not_null<const Row*> row);
void activateByIndex(int index);
enum class Announce {
No,
OnChange,
Always,
};
void setSelected(int index, Announce announce);
void showMenu(int index);
void setForceRippled(not_null<Row*> row, bool rippled);
bool canShare(not_null<const Row*> row) const;
@@ -266,6 +209,26 @@ private:
};
[[nodiscard]] bool ForwardListNavigation(
not_null<QKeyEvent*> e,
not_null<Rows*> rows,
int pageHeight) {
const auto key = e->key();
if (key == Qt::Key_Down) {
rows->selectSkip(1);
} else if (key == Qt::Key_Up) {
rows->selectSkip(-1);
} else if (key == Qt::Key_PageDown || key == Qt::Key_PageUp) {
const auto perPage = std::max(
pageHeight / Rows::DefaultRowHeight(),
1);
rows->selectSkip((key == Qt::Key_PageDown) ? perPage : -perPage);
} else {
return false;
}
return true;
}
class Content : public Ui::RpWidget {
public:
Content(
@@ -373,53 +336,34 @@ Rows::Rows(
update();
setAccessibleName(tr::lng_languages(tr::now));
base::ScreenReaderState::Instance()->activeValue(
) | rpl::on_next([=](bool active) {
setFocusPolicy(active ? Qt::TabFocus : Qt::NoFocus);
}, lifetime());
}
void Rows::focusInEvent(QFocusEvent *e) {
// Select first item or chosen item when focus enters.
if (selected() < 0 && count() > 0) {
const auto chosen = chosenIndex();
setSelected(chosen >= 0 ? chosen : 0);
setSelected(chosen >= 0 ? chosen : 0, Announce::No);
}
RpWidget::focusInEvent(e);
if (base::ScreenReaderState::Instance()->active()) {
const auto index = selected();
if (index >= 0) {
InvokeQueued(this, [=] {
if (selected() != index || !hasFocus()) {
return;
}
const auto index = selected();
if (index >= 0) {
InvokeQueued(this, [=] {
if (selected() == index && hasFocus()) {
accessibilityChildFocused(index);
});
}
}
});
}
}
void Rows::keyPressEvent(QKeyEvent *e) {
const auto pageHeight = window() ? window()->height() : height();
if (ForwardListNavigation(e, this, pageHeight)) {
return;
}
const auto key = e->key();
if (key == Qt::Key_Down) {
selectSkip(1);
} else if (key == Qt::Key_Up) {
selectSkip(-1);
} else if (key == Qt::Key_PageDown || key == Qt::Key_PageUp) {
const auto visibleHeight = visibleRegion().boundingRect().height();
const auto rowsPerPage = std::max(visibleHeight / DefaultRowHeight(), 1);
selectSkip(key == Qt::Key_PageDown ? rowsPerPage : -rowsPerPage);
} else if (key == Qt::Key_Home) {
if (count() > 0) {
setSelected(0);
}
} else if (key == Qt::Key_End) {
if (count() > 0) {
setSelected(count() - 1);
}
if (key == Qt::Key_Home && count() > 0) {
setSelected(0, Announce::Always);
} else if (key == Qt::Key_End && count() > 0) {
setSelected(count() - 1, Announce::Always);
} else if (!e->isAutoRepeat()
&& (key == Qt::Key_Space
|| key == Qt::Key_Return
@@ -697,10 +641,8 @@ void Rows::setForceRippled(not_null<Row*> row, bool rippled) {
void Rows::activateByIndex(int index) {
_chosen = rowByIndex(index).data.id;
_activations.fire_copy(rowByIndex(index).data);
if (base::ScreenReaderState::Instance()->active()) {
accessibilityChildStateChanged(index, { .checked = true });
accessibilityChildNameChanged(index);
}
accessibilityChildStateChanged(index, { .checked = true });
accessibilityChildNameChanged(index);
}
void Rows::leaveEventHook(QEvent *e) {
@@ -795,21 +737,20 @@ void Rows::activateSelected() {
void Rows::selectSkip(int dir) {
const auto limit = count();
auto now = selected();
// If no keyboard selection, start from the checked item.
if (now < 0) {
now = chosenIndex();
}
if (now >= 0) {
const auto changed = now + dir;
if (changed < 0) {
setSelected(0);
setSelected(0, Announce::Always);
} else if (changed >= limit) {
setSelected(limit - 1);
setSelected(limit - 1, Announce::Always);
} else {
setSelected(changed);
setSelected(changed, Announce::Always);
}
} else if (dir > 0) {
setSelected(0);
setSelected(0, Announce::Always);
}
}
@@ -823,27 +764,35 @@ void Rows::changeChosen(const QString &chosen) {
for (const auto &row : _rows) {
row.check->setChecked(row.data.id == chosen, anim::type::normal);
}
if (base::ScreenReaderState::Instance()->active()) {
const auto newIndex = chosenIndex();
if (newIndex != oldIndex && newIndex >= 0) {
accessibilityChildStateChanged(newIndex, { .checked = true });
accessibilityChildNameChanged(newIndex);
}
const auto newIndex = chosenIndex();
if (newIndex != oldIndex && newIndex >= 0) {
accessibilityChildStateChanged(newIndex, { .checked = true });
accessibilityChildNameChanged(newIndex);
}
}
void Rows::setSelected(int selected) {
setSelected(selected, Announce::OnChange);
}
void Rows::setSelected(int selected, Announce announce) {
_mouseSelection = false;
const auto limit = count();
if (selected >= 0 && selected < limit) {
updateSelected(RowSelection{ selected });
const auto clamped = (selected >= 0 && selected < limit)
? selected
: -1;
const auto changed = (indexFromSelection(_selected) != clamped)
|| (clamped < 0 && !v::is_null(_selected));
if (clamped >= 0) {
updateSelected(RowSelection{ clamped });
} else {
updateSelected({});
}
if (selected >= 0 && selected < limit
&& base::ScreenReaderState::Instance()->active()) {
accessibilityChildNameChanged(selected);
accessibilityChildFocused(selected);
const auto shouldAnnounce = (announce == Announce::Always)
|| (announce == Announce::OnChange && changed);
if (shouldAnnounce && clamped >= 0) {
accessibilityChildNameChanged(clamped);
accessibilityChildFocused(clamped);
}
}
@@ -1064,6 +1013,84 @@ void Rows::paintEvent(QPaintEvent *e) {
}
}
QAccessible::Role Rows::accessibilityRole() {
return QAccessible::List;
}
Qt::FocusPolicy Rows::accessibilityFocusPolicy() {
return Qt::TabFocus;
}
QAccessible::Role Rows::accessibilityChildRole() const {
return QAccessible::RadioButton;
}
QAccessible::State Rows::accessibilityChildState(int index) const {
QAccessible::State state;
if (Ui::ScreenReaderModeActive()) {
state.focusable = true;
}
state.checkable = true;
state.checked = (index == chosenIndex());
if (index == selected()) {
state.active = true;
if (hasFocus()) {
state.focused = true;
}
}
return state;
}
int Rows::accessibilityChildCount() const {
return count();
}
QString Rows::accessibilityChildName(int index) const {
if (index < 0 || index >= count()) {
return {};
}
const auto &row = rowByIndex(index);
return row.data.nativeName + u", "_q + row.data.name;
}
QRect Rows::accessibilityChildRect(int index) const {
if (index < 0 || index >= count()) {
return {};
}
const auto &row = rowByIndex(index);
return QRect(0, row.top, width(), row.height);
}
int Rows::accessibilityChildColumnCount(int row) const {
return 2;
}
QAccessible::Role Rows::accessibilityChildSubItemRole() const {
return QAccessible::Cell;
}
QString Rows::accessibilityChildSubItemName(int row, int column) const {
if (column == 0) {
return tr::lng_sr_languages_column_native(tr::now);
} else if (column == 1) {
return tr::lng_sr_languages_column_name(tr::now);
}
return {};
}
QString Rows::accessibilityChildSubItemValue(int row, int column) const {
if (row < 0 || row >= count()) {
return {};
}
const auto &data = rowByIndex(row).data;
if (column == 0) {
return data.nativeName;
} else if (column == 1) {
return data.name;
}
return {};
}
Content::Content(
QWidget *parent,
const Languages &recent,