# Orca # # Copyright 2005-2009 Sun Microsystems Inc. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the # Free Software Foundation, Inc., Franklin Street, Fifth Floor, # Boston MA 02110-1301 USA. # pylint: disable=too-many-lines # pylint: disable=too-many-locals # pylint: disable=too-many-boolean-expressions # pylint: disable=unused-argument # pylint: disable=too-many-arguments # pylint: disable=too-many-positional-arguments # pylint: disable=too-many-instance-attributes """Produces speech presentation for accessible objects.""" from __future__ import annotations import contextlib import urllib.error import urllib.parse import urllib.request from dataclasses import dataclass from typing import TYPE_CHECKING, Any import gi gi.require_version("Atspi", "2.0") from gi.repository import Atspi from . import ( debug, focus_manager, generator, input_event_manager, mathsymbols, messages, object_properties, speech_manager, speech_presenter, speechserver, ) from .acss import ACSS from .ax_document import AXDocument from .ax_hypertext import AXHypertext from .ax_object import AXObject from .ax_table import AXTable from .ax_text import AXText from .ax_utilities import AXUtilities from .ax_value import AXValue from .generator import GeneratorContext, GeneratorMode, WhereAmI from .speechserver import VoiceFamily if TYPE_CHECKING: from collections.abc import Callable from . import script @dataclass(frozen=True) class SpeechGeneratorContext(GeneratorContext): """Settings context for speech generators.""" in_preferences_window: bool only_displayed_text: bool speak_description: bool speak_tutorial_messages: bool speak_position_in_set: bool speak_widget_mnemonic: bool speak_blank_lines: bool speak_indentation: bool announce_cell_headers: bool announce_cell_coordinates: bool announce_spreadsheet_cell_coordinates: bool announce_blockquote: bool announce_form: bool announce_landmark: bool announce_list: bool announce_grouping: bool announce_table: bool class Pause: """A dummy class to indicate we want to insert a pause into an utterance.""" def __init__(self) -> None: pass def __str__(self) -> str: return "PAUSE" PAUSE = [Pause()] DEFAULT = "default" UPPERCASE = "uppercase" HYPERLINK = "hyperlink" SYSTEM = "system" STATE = "state" # Candidate for sound VALUE = "value" # Candidate for sound voice_type = { DEFAULT: speechserver.DEFAULT_VOICE, UPPERCASE: speechserver.UPPERCASE_VOICE, HYPERLINK: speechserver.HYPERLINK_VOICE, SYSTEM: speechserver.SYSTEM_VOICE, STATE: speechserver.SYSTEM_VOICE, # Users may prefer DEFAULT_VOICE here VALUE: speechserver.SYSTEM_VOICE, # Users may prefer DEFAULT_VOICE here } class SpeechGenerator(generator.Generator): """Produces speech presentation for accessible objects.""" _context: SpeechGeneratorContext def __init__(self, script: script.Script) -> None: super().__init__(script, GeneratorMode.SPEECH) @staticmethod def log_generator_output(func): """Decorator for logging.""" def wrapper(*args, **kwargs): result = func(*args, **kwargs) tokens = [f"SPEECH GENERATOR: {func.__name__}:", result] debug.print_tokens(debug.LEVEL_INFO, tokens, True) return result return wrapper def generate_speech( self, obj: Atspi.Accessible, context: SpeechGeneratorContext, **args, ) -> list[Any]: """Generates speech presentation for obj.""" self._context = context rv = self.generate(obj, **args) if rv and not list(filter(lambda x: not isinstance(x, Pause), rv)): tokens = ["SPEECH GENERATOR: Results for", obj, "are pauses only"] debug.print_tokens(debug.LEVEL_INFO, tokens, True) rv = [] return rv def get_name(self, obj: Atspi.Accessible, **args) -> str: """Returns the generated name of obj as a string.""" generated = self._generate_accessible_name(obj, **args) if generated: return generated[0] return "" def get_error_message(self, obj: Atspi.Accessible, **args) -> str: """Returns the generated error message for obj as a string.""" generated = self._generate_state_invalid(obj, **args) if generated: return generated[0] return "" def get_localized_role_name(self, obj: Atspi.Accessible, **args) -> str: if AXUtilities.find_ancestor_inclusive(obj, AXUtilities.is_editable_combo_box): return object_properties.ROLE_EDITABLE_COMBO_BOX if AXUtilities.is_link(obj, args.get("role")) and AXUtilities.is_visited(obj): return object_properties.ROLE_VISITED_LINK return super().get_localized_role_name(obj, **args) def get_role_name(self, obj, **args): """Returns the generated rolename of obj as a string.""" generated = self._generate_accessible_role(obj, **args) if generated: return generated[0] return "" def generate_window_title( self, obj: Atspi.Accessible, context: SpeechGeneratorContext, **args, ) -> list[Any]: """Returns an array of strings the represents details about the window title for obj.""" self._context = context return self._generate_window_title_contents(obj, **args) def _generate_window_title_contents(self, obj: Atspi.Accessible, **args) -> list[Any]: result = [] frame, dialog = self._script.utilities.frame_and_dialog(obj) if frame: frame_result = self._generate_accessible_label_and_name(frame) if not frame_result: frame_result = self._generate_accessible_role(frame) if frame_result: result.append(frame_result) if dialog: if dialog_result := self._generate_accessible_label_and_name(dialog): result.append(dialog_result) elif spreadsheet := AXUtilities.find_ancestor( obj, AXUtilities.is_spreadsheet_table, ): if spreadsheet_result := self._generate_accessible_label_and_name(spreadsheet): result.append(spreadsheet_result) alert_and_dialog_count = len(AXUtilities.get_unfocused_alerts_and_dialogs(obj)) if alert_and_dialog_count > 0: dialogs = [messages.dialog_count_speech(alert_and_dialog_count)] dialogs.extend(self.voice(DEFAULT, obj=obj, **args)) result.append(dialogs) return result def _only_speak_displayed_text(self) -> bool: if self._context is None: return False return self._context.only_displayed_text def _generate_result_separator(self, obj: Atspi.Accessible, **args) -> list[Any]: return PAUSE def _generate_pause(self, obj: Atspi.Accessible, **args) -> list[Any]: if not speech_manager.get_manager().get_insert_pauses_between_utterances() or args.get( "eliminatePauses", False, ): return [] if speech_manager.get_manager().get_punctuation_level() == "all": return [] return PAUSE def _resolve_language_and_dialect( self, obj: Atspi.Accessible | None, language: str, dialect: str, server: speechserver.SpeechServer, family: dict, ) -> tuple[str, str]: """Returns the resolved language and dialect for voice selection.""" if not language: obj_locale = AXObject.get_locale(obj).split(".")[0] if parts := obj_locale.split("_"): language = parts[0] if len(parts) > 1: dialect = parts[1] tokens = ["SPEECH GENERATOR: Reported locale of", obj, "is", obj_locale] debug.print_tokens(debug.LEVEL_INFO, tokens, True) if len(language) <= 1 or not language.isalpha(): language = "" dialect = "" elif len(dialect) <= 1 or not dialect.isalpha(): dialect = "" if not language: language, dialect = server.get_language_and_dialect(family) msg = f"SPEECH GENERATOR: Updated to: '{language}', '{dialect}'" debug.print_message(debug.LEVEL_INFO, msg, True) return language, dialect def _apply_default_voice_overrides( self, voice_props: ACSS, family: dict, obj: Atspi.Accessible | None, context: SpeechGeneratorContext, server: speechserver.SpeechServer, language: str, dialect: str, string: str, ) -> dict: """Applies voice overrides and auto-language-switching for default voice key.""" mgr = speech_manager.get_manager() voice_override: ACSS | None = None if AXUtilities.is_link(obj): voice_override = mgr.get_voice_properties(voice_type[HYPERLINK]) elif isinstance(string, str) and string.isupper() and string.strip().isalpha(): voice_override = mgr.get_voice_properties(voice_type[UPPERCASE]) if voice_override: voice_props.update(voice_override) if ACSS.FAMILY in voice_override: family.update(voice_override[ACSS.FAMILY]) auto_lang_switching = ( not context.in_preferences_window and mgr.get_auto_language_switching() ) # Only update the language if it has changed from the user's preferred voice. # If that occurred, changing the dialect should not be problematic/bothersome. # For now, ignore dialect changes for the same language to avoid two potential # problems: 1) Unexpected voice changes when we have both a user-specified and # object-specified dialect (e.g. American English versus British English). In # the future, we can make this change opt-in. 2) Unexpected voice changes and/or # pauses when either the user or the object lacks a dialect because the families # won't match. if auto_lang_switching and language and language != family.get(VoiceFamily.LANG): family[VoiceFamily.LANG] = language family[VoiceFamily.DIALECT] = dialect if families := server.get_voice_families_for_language( language, dialect, family.get(VoiceFamily.VARIANT), ): family[VoiceFamily.NAME] = families[0][0] else: # On occasions (e.g. SD + espeak + "zh"), we might not get any matching # family. When that occurs, setting/updating the language but leaving the # original name can cause the voice to not be updated, whereas clearing the # name seems to be enough to trigger the correct language to be used. family[VoiceFamily.NAME] = "" tokens = ["SPEECH GENERATOR: Family updated to", family] debug.print_tokens(debug.LEVEL_INFO, tokens, True) return family def voice( self, key: str | None = None, context: SpeechGeneratorContext | None = None, **args, ) -> list[ACSS]: """Returns an array containing a voice.""" effective_context = context or self._context try: assert effective_context is not None except AssertionError: tokens = ["SPEECH GENERATOR: voice called without context for", args.get("obj")] debug.print_tokens(debug.LEVEL_WARNING, tokens, True, True) return [] voicename = voice_type.get(key or DEFAULT, voice_type[DEFAULT]) mgr = speech_manager.get_manager() voice = mgr.get_voice_properties(voice_type[DEFAULT]) obj = args.get("obj") language = args.get("language", "") dialect = args.get("dialect", "") family = voice.get(ACSS.FAMILY, {}).copy() tokens = [ f"SPEECH GENERATOR: {key} voice requested for", obj, f"language='{language}', dialect='{dialect}'", f"Unmodified voice={voice}", ] debug.print_tokens(debug.LEVEL_INFO, tokens, True) server = mgr.get_server() # TODO - JD: We probably never should have gotten to this point if there is no speech # server. Thus we should probably return early in generate_speech() instead. For now, # this check is in place of an assertion that was being reached on a user's system. if server is None: msg = "SPEECH GENERATOR: No speech server available" debug.print_message(debug.LEVEL_INFO, msg, True) return [voice] language, dialect = self._resolve_language_and_dialect( obj, language, dialect, server, family, ) if key in [None, DEFAULT]: family = self._apply_default_voice_overrides( voice, family, obj, effective_context, server, language, dialect, args.get("string", ""), ) else: override = mgr.get_voice_properties(voicename) if override: voice.update(override) if ACSS.FAMILY in override: family = override[ACSS.FAMILY] voice[ACSS.FAMILY] = family tokens = ["SPEECH GENERATOR: Final voice is", voice] debug.print_tokens(debug.LEVEL_INFO, tokens, True) return [voice] def utterances_to_string(self, utterances: list[Any]) -> str: """Converts utterances to a string.""" string = "" for u in utterances: if isinstance(u, str): string += f" {u}" elif isinstance(u, Pause) and string and string[-1].isalnum(): string += "." return string.strip() ################################# BASIC DETAILS ################################# @log_generator_output def _generate_accessible_description(self, obj: Atspi.Accessible, **args) -> list[Any]: if not self._context.speak_description or self._only_speak_displayed_text(): return [] prior_obj = args.get("priorObj") if prior_obj == obj: return [] if AXUtilities.is_tool_tip(prior_obj): return [] result = super()._generate_accessible_description(obj, **args) if result: result[0] = speech_presenter.get_presenter().adjust_for_presentation(obj, result[0]) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_accessible_image_description(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = super()._generate_accessible_image_description(obj, **args) if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_accessible_label(self, obj: Atspi.Accessible, **args) -> list[Any]: result = super()._generate_accessible_label(obj, **args) if result: result.extend(self.voice(DEFAULT, obj=obj, **args)) return result @log_generator_output def _generate_accessible_name(self, obj: Atspi.Accessible, **args) -> list[Any]: is_layered_pane = AXUtilities.is_layered_pane(obj, args.get("role")) if is_layered_pane and self._only_speak_displayed_text(): return [] result = super()._generate_accessible_name(obj, **args) if result: if is_layered_pane: result.extend(self.voice(SYSTEM, obj=obj, **args)) else: result.extend(self.voice(DEFAULT, obj=obj, **args)) return result @log_generator_output def _generate_accessible_label_and_name(self, obj: Atspi.Accessible, **args) -> list[Any]: result = super()._generate_accessible_label_and_name(obj, **args) if result: result[0] = speech_presenter.get_presenter().adjust_for_presentation(obj, result[0]) if len(result) == 1: result.extend(self.voice(DEFAULT, obj=obj, **args)) return result @log_generator_output def _generate_accessible_placeholder_text(self, obj: Atspi.Accessible, **args) -> list[Any]: result = super()._generate_accessible_placeholder_text(obj, **args) if result: result[0] = speech_presenter.get_presenter().adjust_for_presentation(obj, result[0]) result.extend(self.voice(DEFAULT, obj=obj, **args)) return result def _get_ancestor_with_usable_role(self, obj, **args): role = args.get("role", AXObject.get_role(obj)) index = args.get("index", 0) total = args.get("total", 1) def use_ancestor_role(x): if not (AXUtilities.is_heading(x) or AXUtilities.is_link(x)): return False if AXObject.get_role(x) == role: return False return index == total - 1 or AXObject.get_name(x) == AXObject.get_name(obj) return AXUtilities.find_ancestor(obj, use_ancestor_role) def _should_speak_role(self, obj, **args): if self._only_speak_displayed_text(): return False if ( self._context is not None and self._context.active_mode == focus_manager.OBJECT_NAVIGATOR ): return True role = args.get("role", AXObject.get_role(obj)) _enabled, disabled = self._get_enabled_and_disabled_context_roles() do_not_speak = list(disabled) do_not_speak.extend( [ Atspi.Role.ARTICLE, Atspi.Role.EXTENDED, Atspi.Role.FILLER, Atspi.Role.FOOTER, Atspi.Role.FORM, Atspi.Role.LABEL, Atspi.Role.MENU_ITEM, Atspi.Role.PARAGRAPH, Atspi.Role.REDUNDANT_OBJECT, Atspi.Role.SECTION, Atspi.Role.STATIC, Atspi.Role.TABLE_CELL, Atspi.Role.UNKNOWN, ] ) if self._context is not None and not self._context.verbose: do_not_speak.extend([Atspi.Role.CANVAS, Atspi.Role.ICON]) if args.get("string"): do_not_speak.append("ROLE_CONTENT_SUGGESTION") if self._context is not None and self._context.where_am_i_type != WhereAmI.BASIC: do_not_speak.extend([Atspi.Role.LIST, Atspi.Role.LIST_ITEM]) if args.get("startOffset") is not None or args.get("endOffset") is not None: do_not_speak.extend( [ Atspi.Role.ALERT, Atspi.Role.DOCUMENT_FRAME, Atspi.Role.DOCUMENT_PRESENTATION, Atspi.Role.DOCUMENT_SPREADSHEET, Atspi.Role.DOCUMENT_TEXT, Atspi.Role.DOCUMENT_WEB, ] ) if args.get("total", 1) > 1: do_not_speak.append(Atspi.Role.ROW_HEADER) if role in do_not_speak: return False if ( AXUtilities.is_combo_box(AXObject.get_parent(obj)) or AXUtilities.is_anchor(obj) or AXUtilities.is_desktop_frame(obj) or AXUtilities.is_docked_frame(obj) ): return False if AXUtilities.is_panel(obj, role) and ( AXUtilities.is_selected(obj) or not (args.get("ancestorOf") and AXUtilities.is_widget(args.get("ancestorOf"))) ): return False return obj != args.get("priorObj") @log_generator_output def _generate_accessible_role(self, obj: Atspi.Accessible, **args) -> list[Any]: if not self._should_speak_role(obj, **args): return [] parent = AXObject.get_parent(obj) if AXUtilities.is_menu(obj, args.get("role")) and AXUtilities.is_combo_box(parent): return self._generate_accessible_role(parent) if AXUtilities.is_single_line_autocomplete_entry(obj): result: list[Any] = [self.get_localized_role_name(obj, role=Atspi.Role.AUTOCOMPLETE)] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result level = AXUtilities.get_heading_level(obj) if level: result = [ object_properties.ROLE_HEADING_LEVEL_SPEECH % {"role": self.get_localized_role_name(obj, **args), "level": level}, ] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result result = [self.get_localized_role_name(obj, **args)] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_tutorial(self, obj: Atspi.Accessible, **args) -> list[Any]: """Provides the tutorial message for obj.""" if not self._context.speak_tutorial_messages and self._context.where_am_i_type is None: return [] text = AXObject.get_help_text(obj) if not text: return [] return [text, *self.voice(SYSTEM, obj=obj, **args)] @log_generator_output def _generate_has_click_action(self, obj: Atspi.Accessible, **args) -> list[Any]: return [] @log_generator_output def _generate_has_long_description(self, obj: Atspi.Accessible, **args) -> list[Any]: return [] @log_generator_output def _generate_has_details(self, obj: Atspi.Accessible, **args) -> list[Any]: return [] @log_generator_output def _generate_details_for(self, obj: Atspi.Accessible, **args) -> list[Any]: return [] @log_generator_output def _generate_all_details(self, obj: Atspi.Accessible, **args) -> list[Any]: return [] @log_generator_output def _generate_new_radio_button_group(self, obj: Atspi.Accessible, **args) -> list[Any]: if not AXUtilities.is_radio_button(obj): return [] result = super()._generate_radio_button_group(obj, **args) if not result: return [] result.extend(self.voice(DEFAULT, obj=obj, **args)) prior_obj = args.get("priorObj") if not AXUtilities.is_radio_button(prior_obj): return result # TODO - JD: We need other ways to determine group membership. Not all # implementations expose the member-of relation. Gtk3 does. Others are TBD. members = AXUtilities.get_is_member_of(obj) if prior_obj not in members: return result return [] @log_generator_output def _generate_term_value_count(self, obj: Atspi.Accessible, **args) -> list[Any]: result = super()._generate_term_value_count(obj, **args) if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_number_of_children(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text() or not self._context.verbose: return [] if AXUtilities.find_ancestor(obj, AXUtilities.is_tree_or_tree_table): child_nodes = self._script.utilities.child_nodes(obj) result: list[Any] = [messages.item_count(len(child_nodes))] if child_nodes else [] if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result role = args.get("role") if AXUtilities.is_list(obj, role) or AXUtilities.is_list_box(obj, role): set_size = AXUtilities.get_set_size(obj) if set_size: result = [messages.list_item_count(set_size)] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result return [] # TODO - JD: Only presenting the lack of children is what we were doing before the # generator cleanup. This is potentially because we would (optionally) present the # number of children as part of the selected child's presentation. Figure out what # we should really be doing, e.g in the case where no item is selected. if AXUtilities.is_layered_pane(obj, role): children = list(AXObject.iter_children(obj, AXUtilities.is_icon_or_canvas)) if not children: result = [messages.ZERO_ITEMS] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result return [] @log_generator_output def _generate_selected_item_count(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] container = obj if not AXObject.supports_selection(container): container = AXObject.get_parent(obj) if not AXObject.supports_selection(container): return [] result = [] child_count = AXObject.get_child_count(container) selected_count = len(AXUtilities.selected_children(container)) result.append(messages.selected_items_count(selected_count, child_count)) result.extend(self.voice(SYSTEM, obj=obj, **args)) result.append( object_properties.ICON_INDEX_SPEECH % {"index": AXObject.get_index_in_parent(obj) + 1, "total": child_count}, ) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_selected_items(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] container = obj if not AXObject.supports_selection(container): container = AXObject.get_parent(obj) if not AXObject.supports_selection(container): return [] selected_items = AXUtilities.selected_children(container) return list(map(self._generate_accessible_label_and_name, selected_items)) @log_generator_output def _generate_unfocused_dialog_count(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = [] alert_and_dialog_count = len(AXUtilities.get_unfocused_alerts_and_dialogs(obj)) if alert_and_dialog_count > 0: result.append(messages.dialog_count_speech(alert_and_dialog_count)) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result def _get_enabled_and_disabled_context_roles(self): # TODO - JD: Move to the speech and verbosity manager. all_roles = [ Atspi.Role.BLOCK_QUOTE, Atspi.Role.CONTENT_DELETION, Atspi.Role.CONTENT_INSERTION, Atspi.Role.MARK, Atspi.Role.SUGGESTION, "ROLE_DPUB_LANDMARK", "ROLE_DPUB_SECTION", Atspi.Role.DESCRIPTION_LIST, "ROLE_FEED", Atspi.Role.FORM, Atspi.Role.GROUPING, Atspi.Role.LANDMARK, Atspi.Role.LIST, Atspi.Role.PANEL, "ROLE_REGION", Atspi.Role.TABLE, Atspi.Role.TOOL_TIP, ] if self._context is None: return list(all_roles), [] enabled, disabled = [], [] if self._context.announce_blockquote: enabled.append(Atspi.Role.BLOCK_QUOTE) if self._context.announce_landmark: enabled.extend([Atspi.Role.LANDMARK, "ROLE_DPUB_LANDMARK"]) if self._context.announce_list: enabled.append(Atspi.Role.LIST) enabled.append(Atspi.Role.DESCRIPTION_LIST) enabled.append("ROLE_FEED") if self._context.announce_grouping: enabled.extend( [ Atspi.Role.PANEL, Atspi.Role.TOOL_TIP, Atspi.Role.CONTENT_DELETION, Atspi.Role.CONTENT_INSERTION, Atspi.Role.GROUPING, Atspi.Role.MARK, Atspi.Role.SUGGESTION, "ROLE_DPUB_SECTION", ], ) if self._context.announce_form: enabled.append(Atspi.Role.FORM) if self._context.announce_table: enabled.append(Atspi.Role.TABLE) disabled = list(set(all_roles).symmetric_difference(enabled)) return enabled, disabled @log_generator_output def _get_dpub_landmark_leaving_message(self, obj: Atspi.Accessible) -> str: """Returns the leaving message for a DPUB landmark.""" dpub_landmark_checks: list[tuple[Callable, str]] = [ (AXUtilities.is_dpub_acknowledgments, messages.LEAVING_ACKNOWLEDGMENTS), (AXUtilities.is_dpub_afterword, messages.LEAVING_AFTERWORD), (AXUtilities.is_dpub_appendix, messages.LEAVING_APPENDIX), (AXUtilities.is_dpub_bibliography, messages.LEAVING_BIBLIOGRAPHY), (AXUtilities.is_dpub_chapter, messages.LEAVING_CHAPTER), (AXUtilities.is_dpub_conclusion, messages.LEAVING_CONCLUSION), (AXUtilities.is_dpub_credits, messages.LEAVING_CREDITS), (AXUtilities.is_dpub_endnotes, messages.LEAVING_ENDNOTES), (AXUtilities.is_dpub_epilogue, messages.LEAVING_EPILOGUE), (AXUtilities.is_dpub_errata, messages.LEAVING_ERRATA), (AXUtilities.is_dpub_foreword, messages.LEAVING_FOREWORD), (AXUtilities.is_dpub_glossary, messages.LEAVING_GLOSSARY), (AXUtilities.is_dpub_index, messages.LEAVING_INDEX), (AXUtilities.is_dpub_introduction, messages.LEAVING_INTRODUCTION), (AXUtilities.is_dpub_pagelist, messages.LEAVING_PAGELIST), (AXUtilities.is_dpub_part, messages.LEAVING_PART), (AXUtilities.is_dpub_preface, messages.LEAVING_PREFACE), (AXUtilities.is_dpub_prologue, messages.LEAVING_PROLOGUE), (AXUtilities.is_dpub_toc, messages.LEAVING_TOC), ] for predicate, msg in dpub_landmark_checks: if predicate(obj): return msg return "" @staticmethod def _get_dpub_section_leaving_message(obj: Atspi.Accessible) -> str: """Returns the leaving message for a DPUB section.""" dpub_section_checks: list[tuple[Callable, str]] = [ (AXUtilities.is_dpub_abstract, messages.LEAVING_ABSTRACT), (AXUtilities.is_dpub_colophon, messages.LEAVING_COLOPHON), (AXUtilities.is_dpub_credit, messages.LEAVING_CREDIT), (AXUtilities.is_dpub_dedication, messages.LEAVING_DEDICATION), (AXUtilities.is_dpub_epigraph, messages.LEAVING_EPIGRAPH), (AXUtilities.is_dpub_example, messages.LEAVING_EXAMPLE), (AXUtilities.is_dpub_pullquote, messages.LEAVING_PULLQUOTE), (AXUtilities.is_dpub_qna, messages.LEAVING_QNA), ] for predicate, msg in dpub_section_checks: if predicate(obj): return msg return "" @staticmethod def _get_landmark_leaving_message(obj: Atspi.Accessible) -> str: """Returns the leaving message for a landmark.""" landmark_checks: list[tuple[Callable, str]] = [ (AXUtilities.is_landmark_banner, messages.LEAVING_LANDMARK_BANNER), (AXUtilities.is_landmark_complementary, messages.LEAVING_LANDMARK_COMPLEMENTARY), (AXUtilities.is_landmark_contentinfo, messages.LEAVING_LANDMARK_CONTENTINFO), (AXUtilities.is_landmark_main, messages.LEAVING_LANDMARK_MAIN), (AXUtilities.is_landmark_navigation, messages.LEAVING_LANDMARK_NAVIGATION), (AXUtilities.is_landmark_region, messages.LEAVING_LANDMARK_REGION), (AXUtilities.is_landmark_search, messages.LEAVING_LANDMARK_SEARCH), (AXUtilities.is_landmark_form, messages.LEAVING_FORM), ] for predicate, msg in landmark_checks: if predicate(obj): return msg return "" def _generate_leaving(self, obj: Atspi.Accessible, **args) -> list[Any]: if not args.get("leaving"): return [] role = args.get("role", AXObject.get_role(obj)) enabled, _disabled = self._get_enabled_and_disabled_context_roles() is_details = bool(AXUtilities.get_is_details_for(obj)) if not (role in enabled or is_details): return [] count = args.get("count", 1) result = self._get_leaving_message(obj, role, count, is_details) if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result def _get_leaving_message( self, obj: Atspi.Accessible, role: Atspi.Role | str, count: int, is_details: bool, ) -> list[Any]: """Returns the leaving message for the given object and role.""" simple_role_messages: dict[Atspi.Role | str, str] = { "ROLE_FEED": messages.LEAVING_FEED, Atspi.Role.GROUPING: messages.LEAVING_GROUPING, Atspi.Role.FORM: messages.LEAVING_FORM, Atspi.Role.TOOL_TIP: messages.LEAVING_TOOL_TIP, Atspi.Role.CONTENT_DELETION: messages.CONTENT_DELETION_END, Atspi.Role.CONTENT_INSERTION: messages.CONTENT_INSERTION_END, Atspi.Role.MARK: messages.CONTENT_MARK_END, } result = "" if is_details: result = messages.LEAVING_DETAILS elif role == Atspi.Role.BLOCK_QUOTE: result = ( messages.leaving_n_blockquotes(count) if count > 1 else messages.LEAVING_BLOCKQUOTE ) elif AXUtilities.is_document_list(obj): result = messages.leaving_n_lists(count) if count > 1 else messages.LEAVING_LIST elif role == Atspi.Role.PANEL: if AXUtilities.is_figure(obj): result = messages.LEAVING_FIGURE elif AXUtilities.is_document_panel(obj): result = messages.LEAVING_PANEL elif role == Atspi.Role.TABLE and AXUtilities.is_text_document_table(obj): result = messages.LEAVING_TABLE elif role == "ROLE_DPUB_LANDMARK": result = self._get_dpub_landmark_leaving_message(obj) elif role == "ROLE_DPUB_SECTION": result = self._get_dpub_section_leaving_message(obj) elif AXUtilities.is_landmark(obj): result = self._get_landmark_leaving_message(obj) elif role in simple_role_messages: result = simple_role_messages[role] elif role == Atspi.Role.SUGGESTION and not AXUtilities.is_inline_suggestion(obj, role): result = messages.LEAVING_SUGGESTION return [result] def _should_present_common_ancestor( self, obj: Atspi.Accessible, prior_obj: Atspi.Accessible | None, common_ancestor: Atspi.Accessible, present_once: list[Atspi.Role], ) -> bool: """Returns True if the common ancestor's nesting level has changed.""" common_role = self._get_functional_role(common_ancestor) if common_role not in present_once: return False def pred(x: Atspi.Accessible) -> bool: return self._get_functional_role(x) == common_role obj_level = self._get_nesting_level(AXUtilities.find_ancestor(obj, pred)) prior_level = self._get_nesting_level(AXUtilities.find_ancestor(prior_obj, pred)) return obj_level != prior_level def _present_ancestor_results( self, ancestors: list[Atspi.Accessible], ancestor_roles: list[Atspi.Role], obj: Atspi.Accessible, prior_obj: Atspi.Accessible | None, leaving: Any, ) -> list[Any]: """Generates presentations for the collected ancestor chain.""" present_once = [Atspi.Role.BLOCK_QUOTE, Atspi.Role.LIST] result: list[Any] = [] presented_roles: list[Atspi.Role] = [] for i, ancestor in enumerate(ancestors): alt_role = ancestor_roles[i] if alt_role in present_once and alt_role in presented_roles: continue presented_roles.append(alt_role) result.append( self.generate( ancestor, formatType="ancestor", role=alt_role, leaving=leaving, count=ancestor_roles.count(alt_role), ancestorOf=obj, priorObj=prior_obj, ), ) if not leaving: result.reverse() return result def _generate_ancestors(self, obj: Atspi.Accessible, **args) -> list[Any]: leaving = args.get("leaving") if leaving and args.get("priorObj"): prior_obj = obj obj = args.get("priorObj") else: prior_obj = args.get("priorObj") if (prior_obj and AXObject.is_dead(prior_obj)) or AXUtilities.is_tool_tip(prior_obj): return [] if prior_obj and AXObject.get_parent(prior_obj) == AXObject.get_parent(obj): return [] if AXUtilities.is_page_tab(obj) or AXUtilities.is_tool_tip(obj): return [] common_ancestor = AXUtilities.get_common_ancestor(prior_obj, obj) if obj == common_ancestor: return [] include_only = args.get("includeOnly", []) skip_roles = args.get("skipRoles", []) skip_roles.append(Atspi.Role.TREE_ITEM) _enabled, disabled = self._get_enabled_and_disabled_context_roles() skip_roles.extend(disabled) stop_at_roles = args.get("stop_at_roles", []) stop_at_roles.extend([Atspi.Role.APPLICATION, Atspi.Role.MENU_BAR]) stop_after_roles = args.get("stop_after_roles", []) stop_after_roles.extend([Atspi.Role.TOOL_TIP]) present_once = [Atspi.Role.BLOCK_QUOTE, Atspi.Role.LIST] present_common = ( common_ancestor is not None and not leaving and self._should_present_common_ancestor(obj, prior_obj, common_ancestor, present_once) ) ancestors: list[Atspi.Accessible] = [] ancestor_roles: list[Atspi.Role] = [] parent = AXObject.get_parent_checked(obj) while parent: parent_role = self._get_functional_role(parent) if parent_role in stop_at_roles: break # TODO - JD: Create an alternative role for this. should_skip = ( (parent_role in skip_roles and not AXUtilities.is_spreadsheet_table(parent)) or (include_only and parent_role not in include_only) or AXUtilities.is_layout_only(parent) or AXUtilities.is_button_with_popup(parent) ) if ( not should_skip and (parent != common_ancestor or present_common) and not any(AXUtilities.is_redundant_object(a, parent) for a in ancestors) ): ancestors.append(parent) ancestor_roles.append(parent_role) if parent == common_ancestor or parent_role in stop_after_roles: break parent = AXObject.get_parent_checked(parent) return self._present_ancestor_results(ancestors, ancestor_roles, obj, prior_obj, leaving) @log_generator_output def _generate_old_ancestors(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text() or self._script.utilities.in_find_container( self._context.focus, ): return [] prior_obj = args.get("priorObj") if not prior_obj or obj == prior_obj or not AXObject.is_valid(prior_obj): return [] if AXUtilities.is_page_tab(obj): return [] if AXUtilities.get_application(obj) != AXUtilities.get_application( prior_obj, ) or AXUtilities.find_ancestor(obj, lambda x: x == prior_obj): return [] _frame, dialog = self._script.utilities.frame_and_dialog(obj) if dialog: return [] args["leaving"] = True args["includeOnly"] = [ Atspi.Role.BLOCK_QUOTE, Atspi.Role.DESCRIPTION_LIST, Atspi.Role.FORM, Atspi.Role.LANDMARK, Atspi.Role.CONTENT_DELETION, Atspi.Role.CONTENT_INSERTION, Atspi.Role.MARK, Atspi.Role.SUGGESTION, "ROLE_DPUB_LANDMARK", "ROLE_DPUB_SECTION", "ROLE_FEED", Atspi.Role.LIST, Atspi.Role.PANEL, "ROLE_REGION", Atspi.Role.TABLE, Atspi.Role.TOOL_TIP, ] result = [] if AXUtilities.is_block_quote(prior_obj): result.extend( self.generate( prior_obj, role=self._get_functional_role(prior_obj), formatType="focused", leaving=True, ), ) result.extend(self._generate_ancestors(obj, **args)) args.pop("leaving") args.pop("includeOnly") return result @log_generator_output def _generate_new_ancestors(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text() or self._script.utilities.in_find_container( self._context.focus, ): return [] prior_obj = args.get("priorObj") if prior_obj == obj: return [] role = args.get("role", AXObject.get_role(obj)) if ( AXUtilities.is_frame(obj, role) or AXUtilities.is_window(obj, role) or ( AXUtilities.is_menu_item_of_any_kind(obj, role) and (not prior_obj or AXUtilities.is_window(prior_obj)) ) ): return [] if prior_obj is not None: return self._generate_ancestors(obj, **args) frame, dialog = self._script.utilities.frame_and_dialog(obj) top_level = dialog or frame if AXUtilities.is_dialog_or_alert(top_level): return self._generate_ancestors(obj, **args) return [] def generate_context(self, obj, **args): if args.get("priorObj") == obj: return [] result = self._generate_old_ancestors(obj, **args) result.append(self._generate_new_ancestors(obj, **args)) return result def _generate_parent_role_name(self, obj: Atspi.Accessible, **args) -> list[Any]: if args.get("role", AXObject.get_role(obj)) == Atspi.Role.ICON and ( self._context.where_am_i_type is not None ): return [object_properties.ROLE_ICON_PANEL] parent = AXObject.get_parent(obj) if AXUtilities.is_table_cell(parent) or AXUtilities.is_menu(parent): obj = parent return self._generate_accessible_role(AXObject.get_parent(obj)) @log_generator_output def _generate_position_in_list(self, obj: Atspi.Accessible, **args) -> list[Any]: if ( self._only_speak_displayed_text() or not (self._context.speak_position_in_set or args.get("forceList", False)) or args.get("formatType") == "ancestor" ): return [] if args.get("index", 0) + 1 < args.get("total", 1) or obj != self._context.focus: return [] result = [] position = AXUtilities.get_position_in_set(obj) total = AXUtilities.get_set_size(obj) if position < 0: return [] string = object_properties.GROUP_INDEX_SPEECH if total < 0: if not AXUtilities.get_set_size_is_unknown(obj): return [] string = object_properties.GROUP_INDEX_TOTAL_UNKNOWN_SPEECH if total == 1 and AXUtilities.is_menu(obj): return [] position += 1 result.append(string % {"index": position, "total": total}) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result ################################### KEYBOARD ################################### @log_generator_output def _generate_keyboard_accelerator(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result: list[Any] = [] accelerator = AXUtilities.get_accelerator(obj) if accelerator: result.append(accelerator) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_keyboard_mnemonic(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] if not (self._context.speak_widget_mnemonic or args.get("forceMnemonic", False)): return [] if result := super()._generate_keyboard_mnemonic(obj, **args): result.extend(self.voice(SYSTEM, obj=obj, **args)) return result ##################################### LINK ##################################### @log_generator_output def _generate_link_info(self, obj: Atspi.Accessible, **args) -> list[Any]: result = [] link_uri = AXHypertext.get_link_uri(obj) if not link_uri: result.extend(self._generate_accessible_label(obj)) result.extend(self._generate_accessible_role(obj)) result.append(AXText.get_all_text(obj)) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result link_uri_info = urllib.parse.urlparse(link_uri) if link_uri_info[0] in ["ftp", "ftps", "file"]: file_name = link_uri_info[2].split("/") result.append(messages.LINK_TO_FILE % {"uri": link_uri_info[0], "file": file_name[-1]}) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result link_output = messages.LINK_WITH_PROTOCOL % link_uri_info[0] text = AXText.get_all_text(obj) if not AXUtilities.is_visited(obj): link_output = messages.LINK_WITH_PROTOCOL % link_uri_info[0] else: link_output = messages.LINK_WITH_PROTOCOL_VISITED % link_uri_info[0] if not text: text = AXHypertext.get_link_basename(obj) if text: link_output += " " + text result.append(link_output) child = AXObject.get_child(obj, 0) if AXUtilities.is_image(child): result.extend(self._generate_accessible_role(child)) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_link_site_description(self, obj: Atspi.Accessible, **args) -> list[Any]: link_uri = AXHypertext.get_link_uri(obj) if not link_uri: return [] link_uri_info = urllib.parse.urlparse(link_uri) doc_uri = AXDocument.get_uri(self._script.utilities.active_document()) if not doc_uri: return [] result = [] doc_uri_info = urllib.parse.urlparse(doc_uri) if link_uri_info[1] == doc_uri_info[1]: if link_uri_info[2] == doc_uri_info[2]: result.append(messages.LINK_SAME_PAGE) else: result.append(messages.LINK_SAME_SITE) else: link_domain = link_uri_info[1].split(".") doc_domain = doc_uri_info[1].split(".") if ( len(link_domain) > 1 and len(doc_domain) > 1 and link_domain[-1] == doc_domain[-1] and link_domain[-2] == doc_domain[-2] ): result.append(messages.LINK_SAME_SITE) else: result.append(messages.LINK_DIFFERENT_SITE) if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_link_file_size(self, obj: Atspi.Accessible, **args) -> list[Any]: result: list[Any] = [] size_string = "" uri = AXHypertext.get_link_uri(obj) if not uri: return result try: request = urllib.request.Request(uri, method="HEAD") with urllib.request.urlopen(request, timeout=5) as x, contextlib.suppress(KeyError): size_string = x.info()["Content-length"] except (ValueError, urllib.error.URLError, OSError): pass if size_string: size = int(size_string) if size < 10000: result.append(messages.file_size_bytes(size)) elif size < 1000000: result.append(messages.FILE_SIZE_KB % (float(size) * 0.001)) elif size >= 1000000: result.append(messages.FILE_SIZE_MB % (float(size) * 0.000001)) if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result ##################################### MATH ##################################### @log_generator_output def _generate_math_contents(self, obj: Atspi.Accessible, **args) -> list[Any]: result = [] children = list(AXObject.iter_children(obj)) if not children and not AXUtilities.is_math(obj): children = [obj] for child in children: if AXUtilities.is_math_layout_only(child) and AXObject.get_child_count(child): result.extend(self._generate_math_contents(child)) continue result.extend(self.generate(child, role=self._get_functional_role(child))) return result @log_generator_output def _generate_math_enclosed_enclosures(self, obj: Atspi.Accessible, **args) -> list[Any]: enclosure_messages = { "actuarial": messages.MATH_ENCLOSURE_ACTUARIAL, "box": messages.MATH_ENCLOSURE_BOX, "circle": messages.MATH_ENCLOSURE_CIRCLE, "longdiv": messages.MATH_ENCLOSURE_LONGDIV, "radical": messages.MATH_ENCLOSURE_RADICAL, "roundedbox": messages.MATH_ENCLOSURE_ROUNDEDBOX, "horizontalstrike": messages.MATH_ENCLOSURE_HORIZONTALSTRIKE, "verticalstrike": messages.MATH_ENCLOSURE_VERTICALSTRIKE, "downdiagonalstrike": messages.MATH_ENCLOSURE_DOWNDIAGONALSTRIKE, "updiagonalstrike": messages.MATH_ENCLOSURE_UPDIAGONALSTRIKE, "northeastarrow": messages.MATH_ENCLOSURE_NORTHEASTARROW, "bottom": messages.MATH_ENCLOSURE_BOTTOM, "left": messages.MATH_ENCLOSURE_LEFT, "right": messages.MATH_ENCLOSURE_RIGHT, "top": messages.MATH_ENCLOSURE_TOP, "phasorangle": messages.MATH_ENCLOSURE_PHASOR_ANGLE, "madruwb": messages.MATH_ENCLOSURE_MADRUWB, } attrs = AXObject.get_attributes_dict(obj) enclosures = attrs.get("notation", "longdiv").split() strings = [enclosure_messages[e] for e in enclosures if e in enclosure_messages] if not strings: tokens = ["SPEECH GENERATOR: Could not get enclosure message for", enclosures] debug.print_tokens(debug.LEVEL_INFO, tokens, True) return [] if len(strings) == 1: result = [messages.MATH_ENCLOSURE_ENCLOSED_BY % strings[0]] else: strings.insert(-1, messages.MATH_ENCLOSURE_AND) if len(strings) == 3: result = [messages.MATH_ENCLOSURE_ENCLOSED_BY % " ".join(strings)] else: result = [messages.MATH_ENCLOSURE_ENCLOSED_BY % ", ".join(strings)] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_math_fenced_contents(self, obj: Atspi.Accessible, **args) -> list[Any]: result = [] attrs = AXObject.get_attributes_dict(obj) separators = list(attrs.get("separators", ",")) child_count = AXObject.get_child_count(obj) separators.extend(separators[-1] for _ in range(len(separators), child_count - 1)) separators.append("") for i, child in enumerate(AXObject.iter_children(obj)): result.extend(self._generate_math_contents(child, **args)) separator_name = mathsymbols.get_character_name(separators[i]) result.append(separator_name) result.extend(self.voice(DEFAULT, obj=obj, **args)) if separator_name: result.extend(self._generate_pause(obj, **args)) return result @log_generator_output def _generate_math_fraction_numerator(self, obj: Atspi.Accessible, **args) -> list[Any]: numerator = AXObject.get_child(obj, 0) if AXUtilities.is_math_layout_only(numerator): return self._generate_math_contents(numerator) result = self.generate(numerator, role=self._get_functional_role(numerator)) return result @log_generator_output def _generate_math_fraction_denominator(self, obj: Atspi.Accessible, **args) -> list[Any]: denominator = AXObject.get_child(obj, 1) if AXUtilities.is_math_layout_only(denominator): return self._generate_math_contents(denominator) result = self.generate(denominator, role=self._get_functional_role(denominator)) return result @log_generator_output def _generate_math_fraction_line(self, obj: Atspi.Accessible, **args) -> list[Any]: result = [messages.MATH_FRACTION_LINE] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_math_root_base(self, obj: Atspi.Accessible, **args) -> list[Any]: is_square_root = AXUtilities.is_math_square_root(obj) if is_square_root: base = obj else: base = AXObject.get_child(obj, 0) if not base: return [] if ( is_square_root or AXUtilities.is_math_token(base) or AXUtilities.is_math_layout_only(base) ): return self._generate_math_contents(base) result = [self._generate_pause(obj, **args)] result.extend(self.generate(base, role=self._get_functional_role(base))) return result @log_generator_output def _generate_math_script_base(self, obj: Atspi.Accessible, **args) -> list[Any]: base = AXObject.get_child(obj, 0) if not base: return [] return self._generate_math_contents(base) @log_generator_output def _generate_math_script_script(self, obj: Atspi.Accessible, **args) -> list[Any]: if AXUtilities.is_math_layout_only(obj): return self._generate_math_contents(obj) result = self.generate(obj, role=self._get_functional_role(obj)) return result @log_generator_output def _generate_math_script_subscript(self, obj: Atspi.Accessible, **args) -> list[Any]: if AXObject.get_attribute(obj, "tag") == "msup": return [] subscript = AXObject.get_child(obj, 1) if not subscript: return [] result = [messages.MATH_SUBSCRIPT] result.extend(self.voice(SYSTEM, obj=obj, **args)) result.extend(self._generate_math_script_script(subscript)) return result @log_generator_output def _generate_math_script_superscript(self, obj: Atspi.Accessible, **args) -> list[Any]: tag = AXObject.get_attribute(obj, "tag") if tag == "msup": superscript = AXObject.get_child(obj, 1) elif tag == "msubsup": superscript = AXObject.get_child(obj, 2) else: return [] result = [messages.MATH_SUPERSCRIPT] result.extend(self.voice(SYSTEM, obj=obj, **args)) result.extend(self._generate_math_script_script(superscript)) return result @log_generator_output def _generate_math_script_underscript(self, obj: Atspi.Accessible, **args) -> list[Any]: if AXObject.get_attribute(obj, "tag") == "mover": return [] underscript = AXObject.get_child(obj, 1) if not underscript: return [] result = [messages.MATH_UNDERSCRIPT] result.extend(self.voice(SYSTEM, obj=obj, **args)) result.extend(self._generate_math_script_script(underscript)) return result @log_generator_output def _generate_math_script_overscript(self, obj: Atspi.Accessible, **args) -> list[Any]: tag = AXObject.get_attribute(obj, "tag") if tag == "mover": overscript = AXObject.get_child(obj, 1) elif tag == "munderover": overscript = AXObject.get_child(obj, 2) else: return [] result = [messages.MATH_OVERSCRIPT] result.extend(self.voice(SYSTEM, obj=obj, **args)) result.extend(self._generate_math_script_script(overscript)) return result @log_generator_output def _generate_math_script_prescripts(self, obj: Atspi.Accessible, **args) -> list[Any]: prescripts = [] found_separator = False for child in AXObject.iter_children(obj): if AXObject.get_attribute(child, "tag") == "mprescripts": found_separator = True continue if found_separator: prescripts.append(child) result = [] for i, script in enumerate(prescripts): if AXUtilities.is_math_layout_only(script): continue if i % 2: rv = [messages.MATH_PRE_SUPERSCRIPT] else: rv = [messages.MATH_PRE_SUBSCRIPT] rv.extend(self.voice(SYSTEM, obj=obj, **args)) rv.extend(self._generate_math_script_script(script)) result.append(rv) return result @log_generator_output def _generate_math_script_postscripts(self, obj: Atspi.Accessible, **args) -> list[Any]: postscripts = [] child = AXObject.get_child(obj, 1) while child and AXObject.get_attribute(child, "tag") != "mprescripts": postscripts.append(child) child = AXObject.get_next_sibling(child) result = [] for i, script in enumerate(postscripts): if AXUtilities.is_math_layout_only(script): continue if i % 2: rv = [messages.MATH_SUPERSCRIPT] else: rv = [messages.MATH_SUBSCRIPT] rv.extend(self.voice(SYSTEM, obj=obj, **args)) rv.extend(self._generate_math_script_script(script)) result.append(rv) return result @log_generator_output def _generate_math_table_rows(self, obj: Atspi.Accessible, **args) -> list[Any]: result = [] for row in AXObject.iter_children(obj): result.extend(self.generate(row, role=self._get_functional_role(row))) return result ############################### START-OF/END-OF ################################ @log_generator_output def _generate_start_of_deletion(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] start_offset = args.get("startOffset", 0) if start_offset != 0: return [] result = [] suggestion = AXUtilities.find_ancestor(obj, AXUtilities.is_inline_suggestion) if suggestion and obj == AXObject.get_child(suggestion, 0): result.extend([object_properties.ROLE_CONTENT_SUGGESTION]) result.extend(self.voice(SYSTEM, obj=obj, **args)) result.extend(self._generate_pause(obj, **args)) result.extend([messages.CONTENT_DELETION_START]) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_end_of_deletion(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] end_offset = args.get("endOffset") if end_offset is not None: length = AXText.get_character_count(obj) if length and length != end_offset: return [] result = [messages.CONTENT_DELETION_END] result.extend(self.voice(SYSTEM, obj=obj, **args)) suggestion = AXUtilities.find_ancestor(obj, AXUtilities.is_inline_suggestion) if suggestion and obj == AXObject.get_child( suggestion, AXObject.get_child_count(suggestion) - 1, ): result.extend(self._generate_pause(obj, **args)) result.extend([messages.CONTENT_SUGGESTION_END]) result.extend(self.voice(SYSTEM, obj=obj, **args)) container = AXUtilities.find_ancestor(obj, lambda x: bool(AXUtilities.get_details(x))) if AXUtilities.is_suggestion(container): result.extend(self._generate_pause(obj, **args)) result.extend(self._generate_has_details(container)) return result @log_generator_output def _generate_start_of_insertion(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] start_offset = args.get("startOffset", 0) if start_offset != 0: return [] result = [] suggestion = AXUtilities.find_ancestor(obj, AXUtilities.is_inline_suggestion) if suggestion and obj == AXObject.get_child(suggestion, 0): result.extend([object_properties.ROLE_CONTENT_SUGGESTION]) result.extend(self.voice(SYSTEM, obj=obj, **args)) result.extend(self._generate_pause(obj, **args)) result.extend([messages.CONTENT_INSERTION_START]) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_end_of_insertion(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] end_offset = args.get("endOffset") if end_offset is not None: length = AXText.get_character_count(obj) if length and length != end_offset: return [] result = [messages.CONTENT_INSERTION_END] result.extend(self.voice(SYSTEM, obj=obj, **args)) suggestion = AXUtilities.find_ancestor(obj, AXUtilities.is_inline_suggestion) if suggestion and obj == AXObject.get_child( suggestion, AXObject.get_child_count(suggestion) - 1, ): result.extend(self._generate_pause(obj, **args)) result.extend([messages.CONTENT_SUGGESTION_END]) result.extend(self.voice(SYSTEM, obj=obj, **args)) container = AXUtilities.find_ancestor(obj, lambda x: bool(AXUtilities.get_details(x))) if AXUtilities.is_suggestion(container): result.extend(self._generate_pause(obj, **args)) result.extend(self._generate_has_details(container)) return result @log_generator_output def _generate_start_of_mark(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] start_offset = args.get("startOffset", 0) if start_offset != 0: return [] result: list[Any] = [] role_description = AXObject.get_role_description(obj) if role_description: result.append(role_description) result.extend(self.voice(SYSTEM, obj=obj, **args)) result.extend(self._generate_pause(obj, **args)) result.append(messages.CONTENT_MARK_START) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_end_of_mark(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] end_offset = args.get("endOffset") if end_offset is not None: length = AXText.get_character_count(obj) if length and length != end_offset: return [] result = [messages.CONTENT_MARK_END] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_start_of_math_fenced(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] attrs = AXObject.get_attributes_dict(obj) fence_start = attrs.get("open", "(") result = [mathsymbols.get_character_name(fence_start)] result.extend(self.voice(DEFAULT, obj=obj, **args)) return result @log_generator_output def _generate_end_of_math_fenced(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] attrs = AXObject.get_attributes_dict(obj) fence_end = attrs.get("close", ")") result = [mathsymbols.get_character_name(fence_end)] result.extend(self.voice(DEFAULT, obj=obj, **args)) return result @log_generator_output def _generate_start_of_math_fraction(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] if AXUtilities.is_math_fraction_without_bar(obj): result = [messages.MATH_FRACTION_WITHOUT_BAR_START] else: result = [messages.MATH_FRACTION_START] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_end_of_math_fraction(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = [messages.MATH_FRACTION_END] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_start_of_math_root(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = [] if AXUtilities.is_math_square_root(obj): result = [messages.MATH_SQUARE_ROOT_OF] else: index = AXObject.get_child(obj, 1) string = AXText.get_all_text(index) if string == "2": result = [messages.MATH_SQUARE_ROOT_OF] elif string == "3": result = [messages.MATH_CUBE_ROOT_OF] elif string: result = [string] result.extend([messages.MATH_ROOT_OF]) elif AXUtilities.is_math_layout_only(index): result = self._generate_math_contents(index) result.extend([messages.MATH_ROOT_OF]) else: result.extend(self.generate(index, role=self._get_functional_role(index))) result.extend([messages.MATH_ROOT_OF]) if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_end_of_math_root(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = [messages.MATH_ROOT_END] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_start_of_math_table(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] if not AXObject.supports_table(obj): return [] rows = AXTable.get_row_count(obj) columns = AXTable.get_column_count(obj) nesting_level = self._get_nesting_level(obj) if nesting_level > 0: result = [messages.math_nested_table_size(rows, columns)] else: result = [messages.math_table_size(rows, columns)] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_end_of_math_table(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] nesting_level = self._get_nesting_level(obj) if nesting_level > 0: result = [messages.MATH_NESTED_TABLE_END] else: result = [messages.MATH_TABLE_END] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result ##################################### STATE ##################################### @log_generator_output def _generate_state_current(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = super()._generate_state_current(obj, **args) if result: result.extend(self.voice(STATE, obj=obj, **args)) return result @log_generator_output def _generate_state_checked(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = super()._generate_state_checked(obj, **args) if result: result.extend(self.voice(STATE, obj=obj, **args)) return result @log_generator_output def _generate_state_checked_for_switch(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = super()._generate_state_checked_for_switch(obj, **args) if result: result.extend(self.voice(STATE, obj=obj, **args)) return result @log_generator_output def _generate_state_checked_if_checkable(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = super()._generate_state_checked_if_checkable(obj, **args) if result: result.extend(self.voice(STATE, obj=obj, **args)) return result @log_generator_output def _generate_state_expanded(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = super()._generate_state_expanded(obj, **args) if result: result.extend(self.voice(STATE, obj=obj, **args)) return result @log_generator_output def _generate_state_has_popup(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text() or not self._context.verbose: return [] if AXUtilities.is_menu(obj) or not AXUtilities.has_popup(obj): return [] return [messages.HAS_POPUP, *self.voice(SYSTEM, obj=obj, **args)] @log_generator_output def _generate_state_invalid(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = super()._generate_state_invalid(obj, **args) if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_state_multiselectable(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = super()._generate_state_multiselectable(obj, **args) if result: result.extend(self.voice(STATE, obj=obj, **args)) return result @log_generator_output def _generate_state_pressed(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = super()._generate_state_pressed(obj, **args) if result: result.extend(self.voice(STATE, obj=obj, **args)) return result @log_generator_output def _generate_state_read_only(self, obj: Atspi.Accessible, **args) -> list[Any]: result = super()._generate_state_read_only(obj, **args) if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_state_required(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] if args.get("alreadyFocused"): return [] result = super()._generate_state_required(obj, **args) if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_state_selected_for_radio_button(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = super()._generate_state_selected_for_radio_button(obj, **args) if result: result.extend(self.voice(STATE, obj=obj, **args)) return result @log_generator_output def _generate_state_sensitive(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] result = super()._generate_state_sensitive(obj, **args) if result: result.extend(self.voice(STATE, obj=obj, **args)) return result @log_generator_output def _generate_state_unselected(self, obj: Atspi.Accessible, **args) -> list[Any]: role = args.get("role") if ( self._only_speak_displayed_text() or args.get("inMouseReview") or not obj or not AXUtilities.is_selectable(obj) or AXUtilities.is_selected(obj) or AXUtilities.is_text(obj, role) ): return [] if AXUtilities.is_list_item(obj, role): result = [object_properties.STATE_UNSELECTED_LIST_ITEM] result.extend(self.voice(STATE, obj=obj, **args)) return result parent = AXObject.get_parent(obj) table = AXUtilities.get_table(obj) if table: if ( input_event_manager.get_manager().last_event_was_left_or_right() or AXUtilities.is_layout_table(table) or not AXUtilities.is_gui_cell(obj) ): return [] elif AXUtilities.is_layered_pane(parent): if obj in AXUtilities.selected_children(parent): return [] else: return [] result = [object_properties.STATE_UNSELECTED_TABLE_CELL] result.extend(self.voice(STATE, obj=obj, **args)) return result ################################## POSITION ##################################### @log_generator_output def _generate_nesting_level(self, obj: Atspi.Accessible, **args) -> list[Any]: result = super()._generate_nesting_level(obj, **args) if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_tree_item_level(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] args["newOnly"] = True result = super()._generate_tree_item_level(obj, **args) if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result ################################ PROGRESS BARS ################################## @log_generator_output def _generate_progress_bar_index(self, obj: Atspi.Accessible, **args) -> list[Any]: if not args.get("isProgressBarUpdate"): return [] result = [] acc = self._get_most_recent_progress_bar_update()[0] if acc != obj: number = self._get_progress_bar_number_and_count(obj)[0] result = [messages.PROGRESS_BAR_NUMBER % (number)] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_progress_bar_value(self, obj: Atspi.Accessible, **args) -> list[Any]: result = [] percent = AXValue.get_value_as_percent(obj) if percent is not None: result.append(messages.percentage(percent)) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result ##################################### TABLE ##################################### # TODO - JD: This function and fake role really need to die.... @log_generator_output def _generate_real_table_cell(self, obj: Atspi.Accessible, **args) -> list[Any]: result = super()._generate_real_table_cell(obj, **args) if ( not (result and result[0]) and self._context.speak_blank_lines and not args.get("readingRow", False) and args.get("formatType") != "ancestor" ): result.append(messages.BLANK) if result: result.extend(self.voice(DEFAULT, obj=obj, **args)) elif has_formula := self._generate_has_formula(obj, **args): result.extend(self._generate_pause(obj, **args)) result.extend(has_formula) return result @log_generator_output def _generate_table_cell_column_header(self, obj: Atspi.Accessible, **args) -> list[Any]: if not self._context.announce_cell_headers: return [] if self._context.in_say_all: return [] if ( not AXUtilities.cell_column_changed( obj, args.get("priorObj"), ) and self._context.where_am_i_type is None ): return [] args["newOnly"] = not self._get_is_nameless_toggle(obj) result = super()._generate_table_cell_column_header(obj, **args) if result: result.extend(self.voice(DEFAULT, obj=obj, **args)) return result @log_generator_output def _generate_table_cell_row_header(self, obj: Atspi.Accessible, **args) -> list[Any]: if args.get("readingRow"): return [] if not self._context.announce_cell_headers: return [] if self._context.in_say_all: return [] if not AXUtilities.cell_row_changed(obj, args.get("priorObj")) and ( self._context.where_am_i_type is None ): return [] args["newOnly"] = True result = super()._generate_table_cell_row_header(obj, **args) if result: result.extend(self.voice(DEFAULT, obj=obj, **args)) return result @log_generator_output def _generate_table_size(self, obj: Atspi.Accessible, **args) -> list[Any]: if ( self._only_speak_displayed_text() or args.get("leaving") or AXUtilities.is_layout_table(obj) or AXUtilities.is_spreadsheet_table(obj) ): return [] if not self._context.verbose: return self._generate_accessible_role(obj, **args) if AXUtilities.is_text_document_table(obj): role = args.get("role", AXObject.get_role(obj)) _enabled, disabled = self._get_enabled_and_disabled_context_roles() if role in disabled: return [] rows = AXTable.get_row_count(obj) cols = AXTable.get_column_count(obj) if (rows < 0 or cols < 0) and not AXUtilities.get_set_size_is_unknown(obj): return [] result = [messages.table_size(rows, cols)] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_table_sort_order(self, obj: Atspi.Accessible, **args) -> list[Any]: result = super()._generate_table_sort_order(obj, **args) if result: result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_table_cell_column_index(self, obj: Atspi.Accessible, **args) -> list[Any]: if args.get("readingRow"): return [] if not self._context.announce_cell_coordinates: return [] if not AXUtilities.cell_column_changed(obj): return [] col = AXTable.get_cell_coordinates(obj, find_cell=True)[1] if col == -1: return [] result = [messages.TABLE_COLUMN % (col + 1)] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_table_cell_row_index(self, obj: Atspi.Accessible, **args) -> list[Any]: if not AXUtilities.cell_row_changed(obj): return [] if args.get("readingRow"): return [] if not self._context.announce_cell_coordinates: return [] row = AXTable.get_cell_coordinates(obj, find_cell=True)[0] if row == -1: return [] result = [messages.TABLE_ROW % (row + 1)] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_table_cell_position(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] row, col = AXTable.get_cell_coordinates(obj, find_cell=True) if row == -1 or col == -1: return [] table = AXUtilities.get_table(obj) if table is None: return [] result = [] rows = AXTable.get_row_count(table) columns = AXTable.get_column_count(table) result.append(messages.TABLE_COLUMN_DETAILED % {"index": (col + 1), "total": columns}) result.append(messages.TABLE_ROW_DETAILED % {"index": (row + 1), "total": rows}) result.extend(self.voice(SYSTEM, obj=obj, **args)) return result def _generate_has_formula(self, obj: Atspi.Accessible, **args) -> list[Any]: formula = AXUtilities.get_cell_formula(obj) if not formula: return [] if self._context.where_am_i_type == WhereAmI.BASIC: result: list[Any] = [f"{messages.HAS_FORMULA}. {formula}"] else: result = [messages.HAS_FORMULA] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result ##################################### TEXT ###################################### @log_generator_output def _generate_text_substring(self, obj: Atspi.Accessible, **args) -> list[Any]: result = super()._generate_text_substring(obj, **args) if not result: return [] result.extend(self.voice(DEFAULT, obj=obj, **args)) if result[0] in ["\n", ""]: if args.get("total", 1) > 1: return [""] if ( self._context.speak_blank_lines and not self._context.in_say_all and not AXUtilities.is_table_cell_or_header(obj) and args.get("formatType") != "ancestor" ): result[0] = messages.BLANK result[0] = speech_presenter.get_presenter().adjust_for_presentation(obj, result[0]) return result @log_generator_output def _generate_text_line(self, obj: Atspi.Accessible, **args) -> list[Any]: if args.get("inMouseReview") and AXUtilities.is_editable(obj): return [] result = self._generate_text_substring(obj, **args) if result: return result text, start_offset = AXText.get_line_at_offset(obj)[0:2] if text == "\n": if args.get("total", 1) > 1: return [""] if ( self._context.speak_blank_lines and not self._context.in_say_all and not AXUtilities.is_table_cell_or_header(obj) and args.get("formatType") != "ancestor" ): result = [messages.BLANK] result.extend(self.voice(string=text, obj=obj, **args)) return result end_offset = start_offset + len(text) for ( start, _end, string, language, dialect, ) in self._script.utilities.split_substring_by_language(obj, start_offset, end_offset): cleaned_string = string.replace("\ufffc", "") if not cleaned_string: continue args["language"], args["dialect"] = language, dialect if "string" in args: existing = args.get("string") msg = f"INFO: Found existing string '{existing}'; using '{cleaned_string}'" debug.print_message(debug.LEVEL_INFO, msg, True) args.pop("string") voice = self.voice(string=cleaned_string, obj=obj, **args) presenter = speech_presenter.get_presenter() rv: list[Any] = [presenter.adjust_for_presentation(obj, cleaned_string, start)] rv.extend(voice) # TODO - JD: speech.speak() has a bug which causes a list of utterances to # be presented before a string+voice pair that comes first. Until we can # fix speak() properly, we'll avoid triggering it here. # result.append(rv) result.extend(rv) return result @log_generator_output def _generate_text_content(self, obj: Atspi.Accessible, **args) -> list[Any]: result = self._generate_text_substring(obj, **args) if result and result[0]: return result result = super()._generate_text_content(obj, **args) if not (result and result[0]): return [] string = result[0].strip() if len(string) == 1 and AXUtilities.is_math_related(obj): charname = mathsymbols.get_character_name(string) if charname and charname != string: result[0] = charname result[0] = speech_presenter.get_presenter().adjust_for_presentation(obj, result[0]) result.extend(self.voice(DEFAULT, obj=obj, **args)) return result @log_generator_output def _generate_text_selection(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] if not AXUtilities.is_all_text_selected(obj): return [] result = [messages.TEXT_SELECTED] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result @log_generator_output def _generate_text_indentation(self, obj: Atspi.Accessible, **args) -> list[Any]: if not self._context.speak_indentation: return [] if input_event_manager.get_manager().last_event_was_word_navigation(): return [] only_if_changed = None if self._context.where_am_i_type is not None: only_if_changed = False line = AXText.get_line_at_offset(obj, args.get("startOffset"))[0] description = speech_presenter.get_presenter().get_indentation_description( line, only_if_changed, ) if not description: return [] result: list[Any] = [description] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result ##################################### VALUE ##################################### @log_generator_output def _generate_value(self, obj: Atspi.Accessible, **args) -> list[Any]: result = super()._generate_value(obj, **args) if result: result.extend(self.voice(DEFAULT, obj=obj, **args)) return result @log_generator_output def _generate_value_as_percentage(self, obj: Atspi.Accessible, **args) -> list[Any]: if self._only_speak_displayed_text(): return [] percent_value = AXValue.get_value_as_percent(obj) if percent_value is not None: result = [messages.percentage(percent_value)] result.extend(self.voice(SYSTEM, obj=obj, **args)) return result return [] ################################### PER-ROLE ################################### def _generate_default_prefix(self, obj: Atspi.Accessible, **args) -> list[Any]: """Provides the default/role-agnostic information to present before obj.""" if args.get("includeContext") is False: return [] format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_details_for(obj, **args) if format_type == "unfocused": return self._generate_old_ancestors(obj, **args) + self._generate_new_ancestors( obj, **args, ) if self._context.where_am_i_type == WhereAmI.DETAILED: return self._generate_ancestors(obj, **args) return [] def _generate_default_presentation(self, obj: Atspi.Accessible, **args) -> list[Any]: """Provides a default/role-agnostic presentation of obj.""" result = self._generate_default_prefix(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return result result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_default_suffix(self, obj: Atspi.Accessible, **args) -> list[Any]: """Provides the default/role-agnostic information to present after obj.""" if args.get("includeContext") is False: return [] if obj == args.get("priorObj"): return [] # Do not call _generate_accessible_static_text here for ancestors. # The roles of objects which typically have static text we want to # present (panels, groupings, dialogs) already generate it. If we # include it here, it will be double-presented. format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return [] result = [] if self._context.where_am_i_type is not None: result += self._generate_tutorial(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_state_current(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_has_click_action(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_has_details(obj, **args) result += self._generate_details_for(obj, **args) result += self._generate_accessible_description(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_state_has_popup(obj, **args) if cell := AXUtilities.find_ancestor(obj, AXUtilities.is_table_cell): result += self._generate_has_formula(cell, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) if format_type == "unfocused": result += self._generate_tutorial(obj, **args) return result def _generate_accelerator_label(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the accelerator-label role.""" return self._generate_default_presentation(obj, **args) def _generate_alert(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the alert role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_accessible_static_text(obj, **args) return result def _generate_animation(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the animation role.""" return self._generate_default_presentation(obj, **args) def _generate_application(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the application role.""" return self._generate_default_presentation(obj, **args) def _generate_arrow(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the arrow role.""" return self._generate_default_presentation(obj, **args) def _generate_article(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the article role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return result result += self._generate_pause(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_article_in_feed(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the article role when the article is in a feed.""" result = [] result += self._generate_accessible_label_and_name(obj, **args) if not result: result += self._generate_text_line(obj, **args) if not result: result += self._generate_accessible_role(obj, **args) result += self._generate_position_in_list(obj, **args) result += self._generate_default_suffix(obj, **args) return self._generate_default_prefix(obj, **args) + result def _generate_audio(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the audio role.""" return self._generate_default_presentation(obj, **args) def _generate_autocomplete(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the autocomplete role.""" return self._generate_default_presentation(obj, **args) def _generate_block_quote(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the block-quote role.""" result = [] if args.get("priorObj") != obj: result += self._generate_default_prefix(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_nesting_level(obj, **args) result += self._generate_text_line(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_leaving(obj, **args) or result result += self._generate_default_suffix(obj, **args) return result def _generate_calendar(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the calendar role.""" return self._generate_default_presentation(obj, **args) def _generate_canvas(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the canvas role.""" result = self._generate_default_prefix(obj, **args) if self._context.where_am_i_type is not None: result += self._generate_parent_role_name(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_image_description( obj, **args, ) or self._generate_accessible_role(obj, **args) result += self._generate_pause(obj, **args) if self._context.where_am_i_type is not None: result += self._generate_selected_item_count(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_selected_items(obj, **args) else: result += self._generate_position_in_list(obj, **args) result += self._generate_state_unselected(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_caption(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the caption role.""" result = [] if self._generate_text_substring(obj, **args): result += self._generate_text_line(obj, **args) if not result: result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_default_suffix(obj, **args) return self._generate_default_prefix(obj, **args) + result def _generate_chart(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the chart role.""" return self._generate_default_presentation(obj, **args) def _generate_check_box(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the check-box role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_state_checked(obj, **args) result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_state_read_only(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_checked(obj, **args) result += self._generate_state_required(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_state_invalid(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_check_menu_item(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the check-menu-item role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_state_checked(obj, **args) result = self._generate_default_prefix(obj, **args) if self._context.where_am_i_type is not None: result += self._generate_ancestors(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_checked(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_accelerator(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_position_in_list(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_color_chooser(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the color-chooser role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_value(obj, **args) result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_value(obj, **args) if self._context.where_am_i_type is not None: result += self._generate_value_as_percentage(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_column_header(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the column-header role.""" result = [] format_type = args.get("formatType", "unfocused") if format_type != "focused" and self._generate_text_substring(obj, **args): result += self._generate_text_line(obj, **args) if not result: result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_table_sort_order(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_table_cell_row_index(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_table_cell_column_index(obj, **args) result += self._generate_default_suffix(obj, **args) return self._generate_default_prefix(obj, **args) + result def _generate_combo_box(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the combo-box role.""" result = [] label_and_name = self._generate_accessible_label_and_name(obj, **args) result += label_and_name result += self._generate_accessible_role(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: if AXUtilities.is_editable(obj): text_substring = self._generate_text_substring(obj, **args) if text_substring: result += text_substring return result result += self._generate_state_expanded(obj, **args) return result value = self._generate_value(obj, **args) if value and value[0] and value[0] not in label_and_name: result += value result += self._generate_pause(obj, **args) result += self._generate_position_in_list(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_state_required(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_state_invalid(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return self._generate_default_prefix(obj, **args) + result def _generate_comment(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the comment role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return result result += self._generate_pause(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_content_deletion(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the content-deletion role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_leaving(obj, **args) or self._generate_start_of_deletion( obj, **args, ) result = self._generate_default_prefix(obj, **args) result += self._generate_start_of_deletion(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_text_content(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_end_of_deletion(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_content_insertion(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the content-insertion role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_leaving(obj, **args) or self._generate_start_of_insertion( obj, **args, ) result = self._generate_default_prefix(obj, **args) result += self._generate_start_of_insertion(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_text_content(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_end_of_insertion(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_date_editor(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the date-editor role.""" return self._generate_default_presentation(obj, **args) def _generate_definition(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the definition role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_text_content(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_description_list(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the description-list role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: result = self._generate_leaving(obj, **args) if result: return result result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_number_of_children(obj, **args) or self._generate_accessible_role( obj, **args, ) result += self._generate_nesting_level(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_description_term(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the description-term role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return [] result = self._generate_default_prefix(obj, **args) if self._generate_text_substring(obj, **args): result += self._generate_text_line(obj, **args) else: result += self._generate_accessible_label_and_name( obj, **args, ) or self._generate_text_line(obj, **args) if self._context.announce_list: if args.get("index", 0) + 1 < args.get("total", 1): return result result += self._generate_accessible_role(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_term_value_count(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_position_in_list(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_description_value(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the description-value role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return [] result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) or self._generate_text_line( obj, **args, ) if self._context.announce_list: if args.get("index", 0) + 1 < args.get("total", 1): return result result += self._generate_accessible_role(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_position_in_list(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_desktop_frame(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the desktop-frame role.""" return self._generate_default_presentation(obj, **args) def _generate_desktop_icon(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the desktop-icon role.""" return self._generate_icon(obj, **args) def _generate_dial(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the dial role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_value(obj, **args) result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_value(obj, **args) result += self._generate_state_required(obj, **args) result += self._generate_value_as_percentage(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_dialog(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the dialog role.""" result = self._generate_default_prefix(obj, **args) format_type = args.get("formatType", "unfocused") if format_type != "focused": result = self._generate_text_expanding_embedded_objects(obj, **args) if result: return result result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_accessible_static_text(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_directory_pane(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the directory_pane role.""" return self._generate_default_presentation(obj, **args) def _generate_document(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for document-related roles.""" result = [] prior_doc = None if prior_obj := args.get("priorObj"): prior_doc = AXUtilities.find_ancestor_inclusive(prior_obj, AXUtilities.is_document) if prior_doc != obj: result += self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_state_read_only(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_text_line(obj, **args) if prior_doc != obj: result += self._generate_default_suffix(obj, **args) return result def _generate_document_email(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the document-email role.""" return self._generate_document(obj, **args) def _generate_document_frame(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the document-frame role.""" return self._generate_document(obj, **args) def _generate_document_presentation(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the document-presentation role.""" return self._generate_document(obj, **args) def _generate_document_spreadsheet(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the document-spreadsheet role.""" return self._generate_document(obj, **args) def _generate_document_text(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the document-text role.""" return self._generate_document(obj, **args) def _generate_document_web(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the document-web role.""" return self._generate_document(obj, **args) def _generate_dpub_landmark(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the dpub section role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_leaving(obj, **args) or result result += self._generate_pause(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_dpub_section(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the dpub section role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_leaving(obj, **args) or result result += self._generate_pause(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_drawing_area(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the drawing-area role.""" return self._generate_default_presentation(obj, **args) def _generate_editbar(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the editbar role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_state_read_only(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_text_indentation(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_text_selection(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_embedded(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the embedded role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) format_type = args.get("formatType", "unfocused") if format_type != "focused": result += self._generate_text_expanding_embedded_objects( obj, **args, ) or self._generate_accessible_static_text(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_entry(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the entry role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_state_read_only(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_text_indentation(obj, **args) result += self._generate_text_line( obj, **args, ) or self._generate_accessible_placeholder_text(obj, **args) result += self._generate_text_selection(obj, **args) result += self._generate_state_required(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_state_invalid(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_feed(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the feed role.""" result = self._generate_default_prefix(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: result += self._generate_leaving(obj, **args) if result: return result result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_number_of_children(obj, **args) or self._generate_accessible_role( obj, **args, ) result += self._generate_default_suffix(obj, **args) return result def _generate_file_chooser(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the file-chooser role.""" return self._generate_dialog(obj, **args) def _generate_filler(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the filler role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_font_chooser(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the font-chooser role.""" return self._generate_dialog(obj, **args) def _generate_footer(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the footer role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_footnote(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the footnote role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_form(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the form role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_leaving(obj, **args) or ( self._generate_accessible_label_and_name(obj, **args) + self._generate_accessible_role(obj, **args) ) result = self._generate_default_prefix(obj, **args) if self._generate_text_substring(obj, **args): result += self._generate_text_line(obj, **args) else: result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_frame(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the frame role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_accessible_label_and_name(obj, **args) result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_unfocused_dialog_count(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_glass_pane(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the glass-pane role.""" return self._generate_default_presentation(obj, **args) def _generate_grouping(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the grouping role.""" result = self._generate_default_prefix(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: result += self._generate_leaving(obj, **args) if result: return result if self._generate_text_substring(obj, **args): result += self._generate_text_line(obj, **args) if not result: result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_static_text(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_header(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the header role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_heading(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the heading role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_text_content(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_expanded(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_html_container(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the html-container role.""" return self._generate_default_presentation(obj, **args) def _generate_icon(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the icon role.""" result = self._generate_default_prefix(obj, **args) if self._context.where_am_i_type is not None: result += self._generate_parent_role_name(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_image_description( obj, **args, ) or self._generate_accessible_role(obj, **args) result += self._generate_pause(obj, **args) if self._context.where_am_i_type is not None: result += self._generate_selected_item_count(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_selected_items(obj, **args) else: result += self._generate_position_in_list(obj, **args) result += self._generate_state_unselected(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_image(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the image role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_has_long_description(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_image_map(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the image-map role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_info_bar(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the info-bar role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_accessible_static_text(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_input_method_window(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the input-method-window role.""" return self._generate_default_presentation(obj, **args) def _generate_internal_frame(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the internal-frame role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_label(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the label role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label(obj, **args) result += self._generate_text_content(obj) or self._generate_accessible_name(obj) result += self._generate_text_selection(obj) result += self._generate_accessible_role(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_landmark(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the landmark role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: result = self._generate_leaving(obj, **args) if not result: result += self._generate_accessible_role(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) return result result = self._generate_default_prefix(obj, **args) prior_obj = args.get("priorObj") if prior_obj and obj != prior_obj and not AXUtilities.is_ancestor(prior_obj, obj): result += self._generate_accessible_role(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_layered_pane(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the layered-pane role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name( obj, **args, ) or self._generate_accessible_role(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_number_of_children(obj, **args) if self._context.where_am_i_type is None: return result result += self._generate_selected_item_count(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_selected_items(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_level_bar(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the level-bar role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_value(obj, **args) result = [] result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_value(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_link(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the link role.""" result = self._generate_default_prefix(obj, **args) if self._context.where_am_i_type is not None: result += self._generate_link_info(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_link_site_description(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_link_file_size(obj, **args) return result result += self._generate_accessible_label_and_name( obj, **args, ) or self._generate_text_content(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_expanded(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_accelerator(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_list(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the list role.""" result = self._generate_default_prefix(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: result += self._generate_leaving(obj, **args) if result: return result result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_number_of_children(obj, **args) or self._generate_accessible_role( obj, **args, ) if AXUtilities.is_gui_list(obj): result += self._generate_accessible_static_text(obj, **args) else: result += self._generate_nesting_level(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_list_box(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the list-box role.""" result = self._generate_default_prefix(obj, **args) format_type = args.get("formatType", "unfocused") result += self._generate_accessible_label_and_name(obj, **args) if format_type not in ["focused", "ancestor"]: result += self._generate_focused_item(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_state_multiselectable(obj, **args) result += self._generate_number_of_children(obj, **args) or self._generate_accessible_role( obj, **args, ) result += self._generate_default_suffix(obj, **args) return result def _generate_list_item(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the list-item role.""" result = self._generate_default_prefix(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: result += self._generate_state_checked_if_checkable(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_state_expanded(obj, **args) return result result += self._generate_text_line(obj, **args) or self._generate_accessible_label_and_name( obj, **args, ) result += self._generate_accessible_role(obj, **args) result += self._generate_state_checked_if_checkable(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_state_unselected(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_state_expanded(obj, **args) if AXUtilities.is_focusable(obj): result += self._generate_descendants(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_position_in_list(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_log(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the log role.""" return self._generate_default_presentation(obj, **args) def _generate_mark(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the mark role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_leaving(obj, **args) or self._generate_start_of_mark(obj, **args) result = self._generate_default_prefix(obj, **args) result += self._generate_start_of_mark(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_text_content(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_end_of_mark(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_marquee(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the marquee role.""" return self._generate_default_presentation(obj, **args) def _generate_math(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the math role.""" # TODO - JD: Move this logic here. return self._generate_math_contents(obj, **args) def _generate_math_enclosed(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the math-enclosed role.""" result = [] result += self._generate_math_contents(obj, **args) # TODO - JD: Move this logic here. result += self._generate_math_enclosed_enclosures(obj, **args) return result def _generate_math_fenced(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the math-fenced role.""" # TODO - JD: Move the logic from these functions here. result = [] result += self._generate_start_of_math_fenced(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_math_fenced_contents(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_end_of_math_fenced(obj, **args) return result def _generate_math_fraction(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the math-fraction role.""" # TODO - JD: Move the logic from these functions here. result = [] result += self._generate_start_of_math_fraction(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_math_fraction_numerator(obj, **args) result += self._generate_math_fraction_line(obj, **args) result += self._generate_math_fraction_denominator(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_end_of_math_fraction(obj, **args) result += self._generate_pause(obj, **args) return result def _generate_math_multiscript(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the math-multiscript role.""" # TODO - JD: Move the logic from these functions here. result = [] result += self._generate_math_script_base(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_math_script_prescripts(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_math_script_postscripts(obj, **args) result += self._generate_pause(obj, **args) return result def _generate_math_root(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the math-root role.""" # TODO - JD: Move the logic from these functions here. result = [] result += self._generate_start_of_math_root(obj, **args) result += self._generate_math_root_base(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_end_of_math_root(obj, **args) result += self._generate_pause(obj, **args) return result def _generate_math_row(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the math-row role.""" result = [messages.TABLE_ROW % (AXObject.get_index_in_parent(obj) + 1)] result += self.voice(SYSTEM, obj=obj, **args) result += self._generate_pause(obj, **args) for child in AXObject.iter_children(obj): result += self._generate_math_contents(child) result += self._generate_pause(child, **args) return result def _generate_math_script_subsuper(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the math script subsuper role.""" # TODO - JD: Move the logic from these functions here. result = [] result += self._generate_math_script_base(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_math_script_subscript(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_math_script_superscript(obj, **args) result += self._generate_pause(obj, **args) return result def _generate_math_script_underover(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the math script underover role.""" # TODO - JD: Move the logic from these functions here. result = [] result += self._generate_math_script_base(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_math_script_underscript(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_math_script_overscript(obj, **args) result += self._generate_pause(obj, **args) return result def _generate_math_table(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the math-table role.""" # TODO - JD: Move the logic from these functions here. result = [] result += self._generate_start_of_math_table(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_math_table_rows(obj, **args) result += self._generate_end_of_math_table(obj, **args) result += self._generate_pause(obj, **args) return result def _generate_menu(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the menu role.""" result = self._generate_default_prefix(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) return result if self._context.where_am_i_type is not None: result += self._generate_ancestors(obj, **args) or self._generate_parent_role_name( obj, **args, ) result += self._generate_pause(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_expanded(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_accelerator(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_position_in_list(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_menu_bar(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the menu-bar role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_menu_item(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the menu-item role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_state_expanded(obj, **args) result = self._generate_default_prefix(obj, **args) if self._context.where_am_i_type is not None: result += self._generate_ancestors(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_checked_if_checkable(obj, **args) result += self._generate_state_expanded(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_accelerator(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_position_in_list(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_notification(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the notification role.""" # TODO - JD: Should this instead or also be using the logic in get_notification_content()? result = [] result += self._generate_accessible_role(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_text_expanding_embedded_objects( obj, **args, ) or self._generate_accessible_static_text(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_option_pane(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the option-pane role.""" return self._generate_default_presentation(obj, **args) def _generate_page(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the page role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_state_read_only(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_page_tab(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the page-tab role.""" format_type = args.get("formatType", "unfocused") if format_type == "ancestor": result = self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) return result result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_state_expanded(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_position_in_list(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_page_tab_list(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the page-tab-list role.""" return self._generate_default_presentation(obj, **args) def _generate_panel(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the panel role.""" result = self._generate_default_prefix(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: result += self._generate_leaving(obj, **args) if result: return result if self._generate_text_substring(obj, **args): result += self._generate_text_line(obj, **args) if not result: result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_static_text(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_paragraph(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the paragraph role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_state_read_only(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_text_indentation(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_password_text(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the password-text role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_text_selection(obj, **args) result += self._generate_state_required(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_state_invalid(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_popup_menu(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the popup-menu role.""" return self._generate_menu(obj, **args) def _generate_progress_bar(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the progress-bar role.""" result = [] result += self._generate_progress_bar_index(obj, **args) format_type = args.get("formatType", "unfocused") if format_type != "focused": result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_progress_bar_value(obj, **args) or self._generate_accessible_role( obj, **args, ) result += self._generate_default_suffix(obj, **args) return result def _generate_push_button(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the push-button role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_state_expanded(obj, **args) result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_state_expanded(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_accelerator(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_push_button_menu(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the push-button-menu role.""" return self._generate_push_button(obj, **args) def _generate_radio_button(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the radio-button role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_state_selected_for_radio_button(obj, **args) result = self._generate_default_prefix(obj, **args) if self._context.where_am_i_type is not None: result += self._generate_radio_button_group(obj, **args) else: result += self._generate_new_radio_button_group(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_state_selected_for_radio_button(obj, **args) result += self._generate_accessible_role(obj, **args) if not AXUtilities.is_focused(obj): return result result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_position_in_list(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_radio_menu_item(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the radio-menu-item role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_state_selected_for_radio_button(obj, **args) result = self._generate_default_prefix(obj, **args) if self._context.where_am_i_type is not None: result += self._generate_ancestors(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_selected_for_radio_button(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_accelerator(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_position_in_list(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_rating(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the rating role.""" return self._generate_default_presentation(obj, **args) def _generate_region(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the region landmark role.""" result = self._generate_default_prefix(obj, **args) prior_obj = args.get("priorObj") if prior_obj and obj != prior_obj and not AXUtilities.is_ancestor(prior_obj, obj): result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_leaving(obj, **args) or result result += self._generate_pause(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_root_pane(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the root-pane role.""" return self._generate_default_presentation(obj, **args) def _generate_row_header(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the row-header role.""" result = self._generate_default_prefix(obj, **args) format_type = args.get("formatType", "unfocused") if format_type != "focused" and self._generate_text_substring(obj, **args): result += self._generate_text_line(obj, **args) if not result: result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_table_sort_order(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_table_cell_row_index(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_table_cell_column_index(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_ruler(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the ruler role.""" return self._generate_default_presentation(obj, **args) def _generate_scroll_bar(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the scroll-bar role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_value(obj, **args) result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_value(obj, **args) result += self._generate_value_as_percentage(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_scroll_pane(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the scroll-pane role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_section(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the section role.""" format_type = args.get("formatType", "unfocused") result = self._generate_default_prefix(obj, **args) if AXUtilities.is_focusable(obj) and ( AXUtilities.has_explicit_name(obj) or self._context.in_focus_mode ): result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_pause(obj, **args) if format_type == "ancestor": return result result += self._generate_text_indentation(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_separator(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the separator role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_sensitive(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return result result += ( self._generate_accessible_label_and_name(obj, **args) or self._generate_text_content(obj, **args) or self._generate_value(obj, **args) ) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_slider(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the slider role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_value(obj, **args) result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_value(obj, **args) result += self._generate_value_as_percentage(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_spin_button(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the spin-button role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_text_content(obj, **args) or self._generate_value(obj, **args) result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) + result result += self._generate_accessible_role(obj, **args) result += self._generate_text_content(obj, **args) or self._generate_value(obj, **args) result += self._generate_state_required(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_state_invalid(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_split_pane(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the split-pane role.""" format_type = args.get("formatType", "unfocused") if format_type == "focused": return self._generate_value(obj, **args) if format_type == "ancestor": return [] result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_value(obj, **args) result += self._generate_value_as_percentage(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_static(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the static role.""" result = self._generate_default_prefix(obj, **args) if AXUtilities.is_code(obj): result += self._generate_text_indentation(obj, **args) result += self._generate_text_line(obj, **args) else: result += self._generate_text_content(obj, **args) or self._generate_accessible_name( obj, **args, ) result += self._generate_accessible_role(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_status_bar(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the status-bar role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return result content = self._generate_descendants(obj, **args) or self._generate_text_content( obj, **args, ) if not content and AXObject.get_child_count(obj) == 1: content = self._generate_text_content(AXObject.get_child(obj, 0), **args) if content: result += self._generate_pause(obj, **args) + content result += self._generate_default_suffix(obj, **args) return result def _generate_subscript(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the subscript role.""" result = [] result += self._generate_accessible_role(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_suggestion(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the suggestion role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_leaving(obj, **args) or self._generate_accessible_role( obj, **args, ) result = self._generate_default_prefix(obj, **args) result += self._generate_text_content(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_superscript(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the superscript role.""" result = [] result += self._generate_accessible_role(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_switch(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the switch role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_state_checked_for_switch(obj, **args) result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_state_checked_for_switch(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_table(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the table role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: result = self._generate_leaving(obj, **args) if result: return result result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_table_size(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_table_cell(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the table-cell role.""" # TODO - JD: There should be separate generators for each type of cell. result = self._generate_default_prefix(obj, **args) result += self._generate_table_cell_row_header(obj, **args) result += self._generate_table_cell_column_header(obj, **args) result += self._generate_state_checked_for_cell(obj, **args) result += ( self._generate_real_active_descendant_displayed_text(obj, **args) or self._generate_accessible_label_and_name(obj, **args) or self._generate_accessible_image_description(obj, **args) ) result += self._generate_state_expanded(obj, **args) result += self._generate_number_of_children(obj, **args) result += self._generate_state_required(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_state_invalid(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_state_sensitive(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_tree_item_level(obj, **args) result += self._generate_state_unselected(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_table_cell_in_row(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the table-cell role in the context of its row.""" format_type = args.get("formatType", "unfocused") if format_type == "focused": result = self._generate_state_checked_for_cell(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_state_expanded(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_number_of_children(obj, **args) return result if format_type == "ancestor": result = self._generate_table_cell_row_header(obj, **args) result += self._generate_table_cell_column_header(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_table_cell_row_index(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_table_cell_column_index(obj, **args) return result if self._context.where_am_i_type is not None: result = self._generate_row_header(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result = self._generate_column_header(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_table_cell_row(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_table_cell_position(obj, **args) return result result = self._generate_table_cell_row(obj, **args) return result def _generate_table_column_header(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the table-column-header role.""" result = self._generate_default_prefix(obj, **args) format_type = args.get("formatType", "unfocused") if format_type != "focused" and self._generate_text_substring(obj, **args): result += self._generate_text_line(obj, **args) if not result: result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_table_sort_order(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_table_cell_row_index(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_table_cell_column_index(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_table_row(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the table-row role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_state_expanded(obj, **args) result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name( obj, **args, ) or self._generate_text_content(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_state_expanded(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_position_in_list(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_table_row_header(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the table-row-header role.""" result = self._generate_default_prefix(obj, **args) format_type = args.get("formatType", "unfocused") if format_type != "focused" and self._generate_text_substring(obj, **args): result += self._generate_text_line(obj, **args) if not result: result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_table_sort_order(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_table_cell_row_index(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_table_cell_column_index(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_tearoff_menu_item(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the tearoff-menu-item role.""" return self._generate_menu_item(obj, **args) def _generate_terminal(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the terminal role.""" result = self._generate_default_prefix(obj, **args) if self._context.where_am_i_type is None: return result + self._generate_text_line(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_text_line(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_text(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the text role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_state_read_only(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_text_indentation(obj, **args) result += self._generate_text_line( obj, **args, ) or self._generate_accessible_placeholder_text(obj, **args) result += self._generate_text_selection(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_timer(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the timer role.""" return self._generate_default_presentation(obj, **args) def _generate_title_bar(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the title-bar role.""" return self._generate_default_presentation(obj, **args) def _generate_toggle_button(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the toggle-button role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_state_expanded(obj, **args) or self._generate_state_pressed( obj, **args, ) result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_state_expanded(obj, **args) or self._generate_state_pressed( obj, **args, ) result += self._generate_state_sensitive(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_keyboard_mnemonic(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_tool_bar(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the tool-bar role.""" return self._generate_default_presentation(obj, **args) def _generate_tool_tip(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the tool-tip role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_leaving(obj, **args) or self._generate_accessible_role( obj, **args, ) result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_role(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_tree(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the tree role.""" return self._generate_default_presentation(obj, **args) def _generate_tree_item(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the tree-item role.""" format_type = args.get("formatType", "unfocused") if format_type in ["focused", "ancestor"]: return self._generate_state_expanded(obj, **args) result = self._generate_default_prefix(obj, **args) if self._context.where_am_i_type is not None: result += self._generate_ancestors(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_accessible_label_and_name( obj, **args, ) or self._generate_text_content(obj, **args) result += self._generate_pause(obj, **args) result += self._generate_state_expanded(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_position_in_list(obj, **args) if result and not isinstance(result[-1], Pause): result += self._generate_pause(obj, **args) result += self._generate_tree_item_level(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_tree_table(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the tree-table role.""" return self._generate_default_presentation(obj, **args) def _generate_unknown(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the unknown role.""" result = self._generate_default_prefix(obj, **args) result += self._generate_accessible_label_and_name(obj, **args) result += self._generate_default_suffix(obj, **args) return result def _generate_video(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the video role.""" return self._generate_default_presentation(obj, **args) def _generate_viewport(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the viewport role.""" return self._generate_default_presentation(obj, **args) def _generate_window(self, obj: Atspi.Accessible, **args) -> list[Any]: """Generates speech for the window role.""" return self._generate_default_presentation(obj, **args) def generate_contents( # type: ignore[override] self, contents: list[tuple[Atspi.Accessible, int, int, str]], context: SpeechGeneratorContext, **args, ) -> list[Any]: """Generates speech for a list of [obj, start, end, string] contents.""" self._context = context result = [] for content in contents: obj, _start, _end, text = content if not text: continue voices = self.voice(obj=obj, string=text) voice = voices[0] if voices and isinstance(voices, list) else voices result.append([text, voice]) if not result or (len(result) == 1 and result[0][0] == "\n"): if ( self._context.in_say_all or not self._context.speak_blank_lines or args.get("formatType") == "ancestor" ): string = "" else: string = messages.BLANK result = [[string, *self.voice(DEFAULT, **args)]] return result def generate_line( self, obj: Atspi.Accessible, start_offset: int, end_offset: int, line: str, context: SpeechGeneratorContext, ) -> list[Any]: """Generates speech for a line of text, handling language splitting and voice selection.""" if not line or line == "\n": if not self._context.speak_blank_lines: return [] return [messages.BLANK, *self.voice(DEFAULT)] presenter = speech_presenter.get_presenter() split = self._script.utilities.split_substring_by_language(obj, start_offset, end_offset) if not split: text = presenter.adjust_for_presentation(obj, line) return [text, *self.voice(obj=obj, string=text)] result: list[Any] = [] for start, _end, text, language, dialect in split: if not text: continue adjusted_text = presenter.adjust_for_presentation(obj, text, start).lstrip() if not adjusted_text: continue voice = self.voice(obj=obj, string=adjusted_text, language=language, dialect=dialect) result.extend([adjusted_text, *voice]) return result def generate_phrase( self, obj: Atspi.Accessible, start_offset: int, end_offset: int, phrase: str, context: SpeechGeneratorContext, ) -> list[Any]: """Generates speech for a phrase of text with voice selection and text adjustment.""" presenter = speech_presenter.get_presenter() text = presenter.adjust_for_presentation(obj, phrase) if not text: return [] voice = self.voice(obj=obj, string=text) return [text, *voice] def generate_word( self, obj: Atspi.Accessible, offset: int, context: SpeechGeneratorContext, ) -> list[Any]: """Generates speech for a word at offset. Overridden by web for DOM-walking.""" return []