Do not show accessibility configuration warnings for non-focusable controls, account for name fallback.

This commit is contained in:
Pāvels Nadtočajevs
2026-03-09 15:12:48 +02:00
parent a8e37fc010
commit cdb35db426
21 changed files with 214 additions and 110 deletions
+15 -8
View File
@@ -153,20 +153,25 @@ Ref<StyleBox> Button::_get_current_stylebox() const {
return stylebox;
}
String Button::_get_accessibility_name() const {
const String &ac_name = Control::_get_accessibility_name();
if (!xl_text.is_empty() && ac_name.is_empty()) {
return xl_text;
} else if (!xl_text.is_empty() && !ac_name.is_empty() && ac_name != xl_text) {
return ac_name + ": " + xl_text;
} else if (xl_text.is_empty() && ac_name.is_empty() && !get_tooltip_text().is_empty()) {
return get_tooltip_text(); // Fall back to tooltip.
} else {
return ac_name;
}
}
void Button::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
const String &ac_name = get_accessibility_name();
if (!xl_text.is_empty() && ac_name.is_empty()) {
AccessibilityServer::get_singleton()->update_set_name(ae, xl_text);
} else if (!xl_text.is_empty() && !ac_name.is_empty() && ac_name != xl_text) {
AccessibilityServer::get_singleton()->update_set_name(ae, ac_name + ": " + xl_text);
} else if (xl_text.is_empty() && ac_name.is_empty() && !get_tooltip_text().is_empty()) {
AccessibilityServer::get_singleton()->update_set_name(ae, get_tooltip_text()); // Fall back to tooltip.
}
AcceptDialog *dlg = Object::cast_to<AcceptDialog>(get_parent());
if (dlg && dlg->get_ok_button() == this) {
AccessibilityServer::get_singleton()->update_set_role(ae, AccessibilityServerEnums::AccessibilityRole::ROLE_DEFAULT_BUTTON);
@@ -182,6 +187,7 @@ void Button::_notification(int p_what) {
_shape();
update_minimum_size();
update_configuration_warnings();
queue_accessibility_update();
queue_redraw();
} break;
@@ -600,6 +606,7 @@ void Button::set_text(const String &p_text) {
xl_text = translated_text;
_shape();
update_configuration_warnings();
queue_accessibility_update();
queue_redraw();
update_minimum_size();
+2
View File
@@ -120,6 +120,8 @@ protected:
void _notification(int p_what);
static void _bind_methods();
virtual String _get_accessibility_name() const override;
public:
virtual Size2 get_minimum_size() const override;
+23 -17
View File
@@ -263,21 +263,32 @@ PackedStringArray Control::get_configuration_warnings() const {
return warnings;
}
String Control::_get_accessibility_name() const {
if (get_parent_control()) {
String container_info = get_parent_control()->get_accessibility_container_name(this);
return container_info.is_empty() ? get_accessibility_name() : get_accessibility_name() + " " + container_info;
} else {
return get_accessibility_name();
}
}
PackedStringArray Control::get_accessibility_configuration_warnings() const {
ERR_READ_THREAD_GUARD_V(PackedStringArray());
PackedStringArray warnings = Node::get_accessibility_configuration_warnings();
String ac_name = get_accessibility_name().strip_edges();
if (ac_name.is_empty()) {
warnings.push_back(RTR("Accessibility Name must not be empty, or contain only spaces."));
}
if (ac_name.contains(get_class_name())) {
warnings.push_back(RTR("Accessibility Name must not include Node class name."));
}
for (int i = 0; i < ac_name.length(); i++) {
if (is_control(ac_name[i])) {
warnings.push_back(RTR("Accessibility Name must not include control character."));
break;
if (get_focus_mode_with_override() != FOCUS_NONE) {
String ac_name = _get_accessibility_name().strip_edges();
if (ac_name.is_empty()) {
warnings.push_back(RTR("Accessibility Name must not be empty, or contain only spaces."));
}
if (ac_name.contains(get_class_name())) {
warnings.push_back(RTR("Accessibility Name must not include Node class name."));
}
for (int i = 0; i < ac_name.length(); i++) {
if (is_control(ac_name[i])) {
warnings.push_back(RTR("Accessibility Name must not include control character."));
break;
}
}
}
@@ -4040,12 +4051,7 @@ void Control::_notification(int p_notification) {
ERR_FAIL_COND(ae.is_null());
// Base info.
if (get_parent_control()) {
String container_info = get_parent_control()->get_accessibility_container_name(this);
AccessibilityServer::get_singleton()->update_set_name(ae, container_info.is_empty() ? get_accessibility_name() : get_accessibility_name() + " " + container_info);
} else {
AccessibilityServer::get_singleton()->update_set_name(ae, get_accessibility_name());
}
AccessibilityServer::get_singleton()->update_set_name(ae, _get_accessibility_name());
AccessibilityServer::get_singleton()->update_set_description(ae, get_accessibility_description());
AccessibilityServer::get_singleton()->update_set_live(ae, get_accessibility_live());
+2
View File
@@ -681,6 +681,8 @@ public:
void set_accessibility_name(const String &p_name);
String get_accessibility_name() const;
virtual String _get_accessibility_name() const;
void set_accessibility_description(const String &p_description);
String get_accessibility_description() const;
+64 -61
View File
@@ -544,74 +544,77 @@ void GraphNode::gui_input(const Ref<InputEvent> &p_event) {
GraphElement::gui_input(p_event);
}
String GraphNode::_get_accessibility_name() const {
String name = Control::_get_accessibility_name();
if (name.is_empty()) {
name = get_name();
}
name = vformat(ETR("graph node %s (%s)"), name, get_title());
if (slot_table.has(selected_slot)) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
Dictionary type_info;
if (graph) {
type_info = graph->get_type_names();
}
const Slot &slot = slot_table[selected_slot];
name += ", " + vformat(ETR("slot %d of %d"), selected_slot + 1, slot_count);
if (slot.enable_left) {
if (type_info.has(slot.type_left)) {
name += "," + vformat(ETR("input port, type: %s"), type_info[slot.type_left]);
} else {
name += "," + vformat(ETR("input port, type: %d"), slot.type_left);
}
if (graph) {
for (int i = 0; i < left_port_cache.size(); i++) {
if (left_port_cache[i].slot_index == selected_slot) {
String cd = graph->get_connections_description(get_name(), i);
if (cd.is_empty()) {
name += " " + ETR("no connections");
} else {
name += " " + cd;
}
break;
}
}
}
}
if (slot.enable_right) {
if (type_info.has(slot.type_right)) {
name += "," + vformat(ETR("output port, type: %s"), type_info[slot.type_right]);
} else {
name += "," + vformat(ETR("output port, type: %d"), slot.type_right);
}
if (graph) {
for (int i = 0; i < right_port_cache.size(); i++) {
if (right_port_cache[i].slot_index == selected_slot) {
String cd = graph->get_connections_description(get_name(), i);
if (cd.is_empty()) {
name += " " + ETR("no connections");
} else {
name += " " + cd;
}
break;
}
}
}
}
if (graph && graph->is_keyboard_connecting()) {
name += ", " + ETR("currently selecting target port");
}
} else {
name += ", " + vformat(ETR("has %d slots"), slot_count);
}
return name;
}
void GraphNode::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
String name = get_accessibility_name();
if (name.is_empty()) {
name = get_name();
}
name = vformat(ETR("graph node %s (%s)"), name, get_title());
if (slot_table.has(selected_slot)) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
Dictionary type_info;
if (graph) {
type_info = graph->get_type_names();
}
const Slot &slot = slot_table[selected_slot];
name += ", " + vformat(ETR("slot %d of %d"), selected_slot + 1, slot_count);
if (slot.enable_left) {
if (type_info.has(slot.type_left)) {
name += "," + vformat(ETR("input port, type: %s"), type_info[slot.type_left]);
} else {
name += "," + vformat(ETR("input port, type: %d"), slot.type_left);
}
if (graph) {
for (int i = 0; i < left_port_cache.size(); i++) {
if (left_port_cache[i].slot_index == selected_slot) {
String cd = graph->get_connections_description(get_name(), i);
if (cd.is_empty()) {
name += " " + ETR("no connections");
} else {
name += " " + cd;
}
break;
}
}
}
}
if (slot.enable_right) {
if (type_info.has(slot.type_right)) {
name += "," + vformat(ETR("output port, type: %s"), type_info[slot.type_right]);
} else {
name += "," + vformat(ETR("output port, type: %d"), slot.type_right);
}
if (graph) {
for (int i = 0; i < right_port_cache.size(); i++) {
if (right_port_cache[i].slot_index == selected_slot) {
String cd = graph->get_connections_description(get_name(), i);
if (cd.is_empty()) {
name += " " + ETR("no connections");
} else {
name += " " + cd;
}
break;
}
}
}
}
if (graph && graph->is_keyboard_connecting()) {
name += ", " + ETR("currently selecting target port");
}
} else {
name += ", " + vformat(ETR("has %d slots"), slot_count);
}
AccessibilityServer::get_singleton()->update_set_role(ae, AccessibilityServerEnums::AccessibilityRole::ROLE_LIST);
AccessibilityServer::get_singleton()->update_set_name(ae, name);
AccessibilityServer::get_singleton()->update_add_custom_action(ae, CustomAccessibilityAction::ACTION_CONNECT_INPUT, ETR("Edit Input Port Connection"));
AccessibilityServer::get_singleton()->update_add_custom_action(ae, CustomAccessibilityAction::ACTION_CONNECT_OUTPUT, ETR("Edit Output Port Connection"));
AccessibilityServer::get_singleton()->update_add_custom_action(ae, CustomAccessibilityAction::ACTION_FOLLOW_INPUT, ETR("Follow Input Port Connection"));
+2
View File
@@ -127,6 +127,8 @@ protected:
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
virtual String _get_accessibility_name() const override;
public:
virtual String get_accessibility_container_name(const Node *p_node) const override;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
+10 -3
View File
@@ -1214,6 +1214,15 @@ void LineEdit::_accessibility_action_menu(const Variant &p_data) {
menu->grab_focus();
}
String LineEdit::_get_accessibility_name() const {
const String &ac_name = Control::_get_accessibility_name();
if (!placeholder.is_empty() && ac_name.is_empty()) {
return atr(placeholder);
} else {
return ac_name;
}
}
void LineEdit::_notification(int p_what) {
switch (p_what) {
#ifdef TOOLS_ENABLED
@@ -1242,9 +1251,6 @@ void LineEdit::_notification(int p_what) {
if (using_placeholder && !placeholder.is_empty()) {
AccessibilityServer::get_singleton()->update_set_placeholder(ae, atr(placeholder));
}
if (!placeholder.is_empty() && get_accessibility_name().is_empty()) {
AccessibilityServer::get_singleton()->update_set_name(ae, atr(placeholder));
}
AccessibilityServer::get_singleton()->update_set_flag(ae, AccessibilityServerEnums::AccessibilityFlags::FLAG_READONLY, !editable);
AccessibilityServer::get_singleton()->update_add_action(ae, AccessibilityServerEnums::AccessibilityAction::ACTION_SET_TEXT_SELECTION, callable_mp(this, &LineEdit::_accessibility_action_set_selection));
@@ -2269,6 +2275,7 @@ void LineEdit::set_placeholder(String p_text) {
placeholder_translated = atr(placeholder);
_shape();
queue_redraw();
update_configuration_warnings();
}
String LineEdit::get_placeholder() const {
+2
View File
@@ -297,6 +297,8 @@ protected:
void _accessibility_action_set_value(const Variant &p_data);
void _accessibility_action_menu(const Variant &p_data);
virtual String _get_accessibility_name() const override;
public:
void edit(bool p_hide_focus = false);
void unedit();
+16 -8
View File
@@ -62,6 +62,7 @@ void LinkButton::set_text(const String &p_text) {
text = p_text;
xl_text = atr(text);
_shape();
update_configuration_warnings();
update_minimum_size();
queue_redraw();
}
@@ -206,6 +207,19 @@ Control::CursorShape LinkButton::get_cursor_shape(const Point2 &p_pos) const {
return is_disabled() ? CURSOR_ARROW : get_default_cursor_shape();
}
String LinkButton::_get_accessibility_name() const {
const String &ac_name = Control::_get_accessibility_name();
if (!xl_text.is_empty() && ac_name.is_empty()) {
return xl_text;
} else if (!xl_text.is_empty() && !ac_name.is_empty() && ac_name != xl_text) {
return ac_name + ": " + xl_text;
} else if (xl_text.is_empty() && ac_name.is_empty() && !get_tooltip_text().is_empty()) {
return get_tooltip_text(); // Fall back to tooltip.
} else {
return ac_name;
}
}
void LinkButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
@@ -213,20 +227,14 @@ void LinkButton::_notification(int p_what) {
ERR_FAIL_COND(ae.is_null());
AccessibilityServer::get_singleton()->update_set_role(ae, AccessibilityServerEnums::AccessibilityRole::ROLE_LINK);
const String &ac_name = get_accessibility_name();
if (!xl_text.is_empty() && ac_name.is_empty()) {
AccessibilityServer::get_singleton()->update_set_name(ae, xl_text);
} else if (!xl_text.is_empty() && !ac_name.is_empty() && ac_name != xl_text) {
AccessibilityServer::get_singleton()->update_set_name(ae, ac_name + ": " + xl_text);
} else if (xl_text.is_empty() && ac_name.is_empty() && !get_tooltip_text().is_empty()) {
AccessibilityServer::get_singleton()->update_set_name(ae, get_tooltip_text()); // Fall back to tooltip.
}
AccessibilityServer::get_singleton()->update_set_url(ae, uri);
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
xl_text = atr(text);
_shape();
update_configuration_warnings();
update_minimum_size();
queue_redraw();
} break;
+2
View File
@@ -84,6 +84,8 @@ protected:
void _notification(int p_what);
static void _bind_methods();
virtual String _get_accessibility_name() const override;
public:
void set_text(const String &p_text);
String get_text() const;
+2
View File
@@ -708,6 +708,7 @@ void MenuBar::remove_child_notify(Node *p_child) {
menu_cache.remove_at(idx);
p_child->remove_meta("_menu_name");
p_child->update_configuration_warnings();
p_child->remove_meta("_menu_tooltip");
p_child->disconnect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
@@ -900,6 +901,7 @@ void MenuBar::set_menu_title(int p_menu, const String &p_title) {
} else {
pm->set_meta("_menu_name", p_title);
}
pm->update_configuration_warnings();
menu_cache.write[p_menu].name = p_title;
shape(menu_cache.write[p_menu]);
if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) {
+8 -3
View File
@@ -1295,6 +1295,14 @@ RID PopupMenu::get_focused_accessibility_element() const {
}
}
String PopupMenu::_get_accessibility_name() const {
if (has_meta("_menu_name")) {
return get_meta("_menu_name", get_name());
} else {
return Window::_get_accessibility_name();
}
}
void PopupMenu::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_EXIT_TREE: {
@@ -1315,9 +1323,6 @@ void PopupMenu::_notification(int p_what) {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
if (has_meta("_menu_name")) {
AccessibilityServer::get_singleton()->update_set_name(ae, get_meta("_menu_name", get_name()));
}
AccessibilityServer::get_singleton()->update_set_role(ae, AccessibilityServerEnums::AccessibilityRole::ROLE_MENU);
AccessibilityServer::get_singleton()->update_set_list_item_count(ae, items.size());
+2
View File
@@ -272,6 +272,8 @@ protected:
bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
virtual String _get_accessibility_name() const override;
#ifndef DISABLE_DEPRECATED
void _add_shortcut_bind_compat_36493(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
void _add_icon_shortcut_bind_compat_36493(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
+9 -1
View File
@@ -54,6 +54,15 @@ void SpinBoxLineEdit::_accessibility_action_dec(const Variant &p_data) {
}
}
String SpinBoxLineEdit::_get_accessibility_name() const {
SpinBox *parent_sb = Object::cast_to<SpinBox>(get_parent());
if (parent_sb) {
return parent_sb->_get_accessibility_name();
} else {
return Control::_get_accessibility_name();
}
}
void SpinBoxLineEdit::_notification(int p_what) {
ERR_MAIN_THREAD_GUARD;
switch (p_what) {
@@ -64,7 +73,6 @@ void SpinBoxLineEdit::_notification(int p_what) {
SpinBox *parent_sb = Object::cast_to<SpinBox>(get_parent());
if (parent_sb) {
AccessibilityServer::get_singleton()->update_set_role(ae, AccessibilityServerEnums::AccessibilityRole::ROLE_SPIN_BUTTON);
AccessibilityServer::get_singleton()->update_set_name(ae, parent_sb->get_accessibility_name());
AccessibilityServer::get_singleton()->update_set_description(ae, parent_sb->get_accessibility_description());
AccessibilityServer::get_singleton()->update_set_live(ae, parent_sb->get_accessibility_live());
AccessibilityServer::get_singleton()->update_set_num_value(ae, parent_sb->get_value());
+2
View File
@@ -42,6 +42,8 @@ protected:
void _accessibility_action_inc(const Variant &p_data);
void _accessibility_action_dec(const Variant &p_data);
virtual String _get_accessibility_name() const override;
};
class SpinBox : public Range {
+4 -1
View File
@@ -322,6 +322,10 @@ void SplitContainerDragger::stop_dragging() {
}
}
String SplitContainerDragger::_get_accessibility_name() const {
return RTR("Drag to resize");
}
void SplitContainerDragger::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ACCESSIBILITY_UPDATE: {
@@ -332,7 +336,6 @@ void SplitContainerDragger::_notification(int p_what) {
ERR_FAIL_COND(ae.is_null());
AccessibilityServer::get_singleton()->update_set_role(ae, AccessibilityServerEnums::AccessibilityRole::ROLE_SPLITTER);
AccessibilityServer::get_singleton()->update_set_name(ae, RTR("Drag to resize"));
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
if (sc->collapsed || sc->valid_children.size() < 2u || !sc->dragging_enabled) {
+2
View File
@@ -81,6 +81,8 @@ protected:
void _accessibility_action_dec(const Variant &p_data);
void _accessibility_action_set_value(const Variant &p_data);
virtual String _get_accessibility_name() const override;
public:
int dragger_index = -1;
+10 -3
View File
@@ -742,6 +742,14 @@ Ref<StyleBox> TextEdit::_get_current_stylebox() const {
return editable ? theme_cache.style_normal : theme_cache.style_readonly;
}
String TextEdit::_get_accessibility_name() const {
if (!placeholder_text.is_empty() && get_accessibility_name().is_empty()) {
return atr(placeholder_text);
} else {
return Control::_get_accessibility_name();
}
}
void TextEdit::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_EXIT_TREE:
@@ -757,9 +765,7 @@ void TextEdit::_notification(int p_what) {
if (text.size() == 1 && text[0].is_empty()) {
AccessibilityServer::get_singleton()->update_set_placeholder(ae, atr(placeholder_text));
}
if (!placeholder_text.is_empty() && get_accessibility_name().is_empty()) {
AccessibilityServer::get_singleton()->update_set_name(ae, atr(placeholder_text));
}
AccessibilityServer::get_singleton()->update_set_flag(ae, AccessibilityServerEnums::AccessibilityFlags::FLAG_READONLY, !editable);
AccessibilityServer::get_singleton()->update_add_action(ae, AccessibilityServerEnums::AccessibilityAction::ACTION_SET_TEXT_SELECTION, callable_mp(this, &TextEdit::_accessibility_action_set_selection));
AccessibilityServer::get_singleton()->update_add_action(ae, AccessibilityServerEnums::AccessibilityAction::ACTION_REPLACE_SELECTED_TEXT, callable_mp(this, &TextEdit::_accessibility_action_replace_selected));
@@ -4046,6 +4052,7 @@ void TextEdit::set_placeholder(const String &p_text) {
placeholder_text = p_text;
_update_placeholder();
update_configuration_warnings();
queue_accessibility_update();
queue_redraw();
}
+2
View File
@@ -708,6 +708,8 @@ protected:
virtual void _draw_guidelines() {}
virtual void _update_theme_item_cache() override;
virtual String _get_accessibility_name() const override;
/* Internal API for CodeEdit, pending public API. */
// Brace matching.
struct BraceMatchingData {
+31 -5
View File
@@ -1514,6 +1514,35 @@ RID Window::get_focused_accessibility_element() const {
return Node::get_focused_accessibility_element();
}
String Window::_get_accessibility_name() const {
if (accessibility_name.is_empty()) {
return displayed_title;
} else {
return accessibility_name;
}
}
PackedStringArray Window::get_accessibility_configuration_warnings() const {
ERR_READ_THREAD_GUARD_V(PackedStringArray());
PackedStringArray warnings = Node::get_accessibility_configuration_warnings();
String ac_name = _get_accessibility_name().strip_edges();
if (ac_name.is_empty()) {
warnings.push_back(RTR("Accessibility Name must not be empty, or contain only spaces."));
}
if (ac_name.contains(get_class_name())) {
warnings.push_back(RTR("Accessibility Name must not include Node class name."));
}
for (int i = 0; i < ac_name.length(); i++) {
if (is_control(ac_name[i])) {
warnings.push_back(RTR("Accessibility Name must not include control character."));
break;
}
}
return warnings;
}
void Window::_notification(int p_what) {
ERR_MAIN_THREAD_GUARD;
switch (p_what) {
@@ -1533,11 +1562,7 @@ void Window::_notification(int p_what) {
ERR_FAIL_COND(ae.is_null());
AccessibilityServer::get_singleton()->update_set_role(ae, AccessibilityServerEnums::AccessibilityRole::ROLE_WINDOW);
if (accessibility_name.is_empty()) {
AccessibilityServer::get_singleton()->update_set_name(ae, displayed_title);
} else {
AccessibilityServer::get_singleton()->update_set_name(ae, accessibility_name);
}
AccessibilityServer::get_singleton()->update_set_name(ae, _get_accessibility_name());
AccessibilityServer::get_singleton()->update_set_description(ae, accessibility_description);
AccessibilityServer::get_singleton()->update_set_flag(ae, AccessibilityServerEnums::AccessibilityFlags::FLAG_MODAL, exclusive);
AccessibilityServer::get_singleton()->update_add_action(ae, AccessibilityServerEnums::AccessibilityAction::ACTION_FOCUS, callable_mp(this, &Window::_accessibility_action_grab_focus));
@@ -3319,6 +3344,7 @@ void Window::_update_displayed_title() {
}
#endif
update_configuration_warnings();
queue_accessibility_update();
}
+4
View File
@@ -288,6 +288,8 @@ protected:
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
virtual String _get_accessibility_name() const;
GDVIRTUAL0RC(Vector2, _get_contents_minimum_size)
public:
@@ -297,6 +299,8 @@ public:
NOTIFICATION_THEME_CHANGED = 32
};
PackedStringArray get_accessibility_configuration_warnings() const override;
static void set_root_layout_direction(int p_root_dir);
static Window *get_from_id(DisplayServerEnums::WindowID p_window_id);