# Orca
#
# Copyright 2009 Sun Microsystems Inc.
# Copyright 2015-2016 Igalia, S.L.
#
# 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=wrong-import-position
# pylint: disable=too-many-return-statements
# pylint: disable=broad-exception-caught
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements

"""Superclass of classes used to generate presentations for objects."""

__id__        = "$Id:$"
__version__   = "$Revision:$"
__date__      = "$Date:$"
__copyright__ = "Copyright (c) 2009 Sun Microsystems Inc." \
                "Copyright (c) 2015-2016 Igalia, S.L."
__license__   = "LGPL"

import time
import threading
from difflib import SequenceMatcher

import gi
gi.require_version("Atspi", "2.0")
from gi.repository import Atspi

from . import braille
from . import debug
from . import focus_manager
from . import object_properties
from . import settings
from . import settings_manager
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

class Generator:
    """Superclass of classes used to generate presentations for objects."""

    CACHED_DESCRIPTION: dict = {}
    CACHED_IMAGE_DESCRIPTION: dict = {}
    CACHED_IS_NAMELESS_TOGGLE: dict = {}
    CACHED_NESTING_LEVEL: dict = {}
    CACHED_STATIC_TEXT: dict = {}
    CACHED_TEXT_SUBSTRING: dict = {}
    CACHED_TEXT_LINE: dict = {}
    CACHED_TEXT: dict = {}
    CACHED_TEXT_EXPANDING_EOCS: dict = {}
    CACHED_TREE_ITEM_LEVEL: dict = {}
    USED_DESCRIPTION_FOR_NAME: dict = {}
    USED_DESCRIPTION_FOR_STATIC_TEXT: dict = {}

    _lock = threading.Lock()

    def __init__(self, script, mode):
        self._mode = mode
        self._script = script
        self._active_progress_bars = {}
        self._generators = {
            Atspi.Role.ALERT: self._generate_alert,
            Atspi.Role.ANIMATION: self._generate_animation,
            Atspi.Role.ARTICLE: self._generate_article,
            "ROLE_ARTICLE_IN_FEED": self._generate_article_in_feed,
            Atspi.Role.BLOCK_QUOTE: self._generate_block_quote,
            Atspi.Role.CANVAS: self._generate_canvas,
            Atspi.Role.CAPTION: self._generate_caption,
            Atspi.Role.CHECK_BOX: self._generate_check_box,
            Atspi.Role.CHECK_MENU_ITEM: self._generate_check_menu_item,
            Atspi.Role.COLOR_CHOOSER: self._generate_color_chooser,
            Atspi.Role.COLUMN_HEADER: self._generate_column_header,
            Atspi.Role.COMBO_BOX: self._generate_combo_box,
            Atspi.Role.COMMENT: self._generate_comment,
            Atspi.Role.CONTENT_DELETION: self._generate_content_deletion,
            "ROLE_CONTENT_ERROR": self._generate_content_error,
            Atspi.Role.CONTENT_INSERTION: self._generate_content_insertion,
            Atspi.Role.DEFINITION: self._generate_definition,
            Atspi.Role.DESCRIPTION_LIST: self._generate_description_list,
            Atspi.Role.DESCRIPTION_TERM: self._generate_description_term,
            Atspi.Role.DESCRIPTION_VALUE: self._generate_description_value,
            Atspi.Role.DIAL: self._generate_dial,
            Atspi.Role.DIALOG: self._generate_dialog,
            Atspi.Role.DOCUMENT_EMAIL: self._generate_document_email,
            Atspi.Role.DOCUMENT_FRAME: self._generate_document_frame,
            Atspi.Role.DOCUMENT_PRESENTATION: self._generate_document_presentation,
            Atspi.Role.DOCUMENT_SPREADSHEET: self._generate_document_spreadsheet,
            Atspi.Role.DOCUMENT_TEXT: self._generate_document_text,
            Atspi.Role.DOCUMENT_WEB: self._generate_document_web,
            "ROLE_DPUB_LANDMARK": self._generate_dpub_landmark,
            "ROLE_DPUB_SECTION": self._generate_dpub_section,
            Atspi.Role.EDITBAR: self._generate_editbar,
            Atspi.Role.EMBEDDED: self._generate_embedded,
            Atspi.Role.ENTRY: self._generate_entry,
            "ROLE_FEED": self._generate_feed,
            Atspi.Role.FOOTNOTE: self._generate_footnote,
            Atspi.Role.FOOTER: self._generate_footer,
            Atspi.Role.FORM: self._generate_form,
            Atspi.Role.FRAME: self._generate_frame,
            Atspi.Role.GROUPING: self._generate_grouping,
            Atspi.Role.HEADER: self._generate_header,
            Atspi.Role.HEADING: self._generate_heading,
            Atspi.Role.ICON: self._generate_icon,
            Atspi.Role.IMAGE: self._generate_image,
            Atspi.Role.INFO_BAR: self._generate_info_bar,
            Atspi.Role.INTERNAL_FRAME: self._generate_internal_frame,
            Atspi.Role.LABEL: self._generate_label,
            Atspi.Role.LANDMARK: self._generate_landmark,
            Atspi.Role.LAYERED_PANE: self._generate_layered_pane,
            Atspi.Role.LINK: self._generate_link,
            Atspi.Role.LEVEL_BAR: self._generate_level_bar,
            Atspi.Role.LIST: self._generate_list,
            Atspi.Role.LIST_BOX: self._generate_list_box,
            Atspi.Role.LIST_ITEM: self._generate_list_item,
            Atspi.Role.MATH: self._generate_math,
            "ROLE_MATH_ENCLOSED": self._generate_math_enclosed,
            "ROLE_MATH_FENCED": self._generate_math_fenced,
            Atspi.Role.MATH_FRACTION: self._generate_math_fraction,
            Atspi.Role.MATH_ROOT: self._generate_math_root,
            "ROLE_MATH_MULTISCRIPT": self._generate_math_multiscript,
            "ROLE_MATH_SCRIPT_SUBSUPER": self._generate_math_script_subsuper,
            "ROLE_MATH_SCRIPT_UNDEROVER": self._generate_math_script_underover,
            "ROLE_MATH_TABLE": self._generate_math_table,
            "ROLE_MATH_TABLE_ROW": self._generate_math_row,
            Atspi.Role.MARK: self._generate_mark,
            Atspi.Role.MENU: self._generate_menu,
            Atspi.Role.MENU_ITEM: self._generate_menu_item,
            Atspi.Role.NOTIFICATION: self._generate_notification,
            Atspi.Role.PAGE: self._generate_page,
            Atspi.Role.PAGE_TAB: self._generate_page_tab,
            Atspi.Role.PANEL: self._generate_panel,
            Atspi.Role.PARAGRAPH: self._generate_paragraph,
            Atspi.Role.PASSWORD_TEXT: self._generate_password_text,
            Atspi.Role.PROGRESS_BAR: self._generate_progress_bar,
            Atspi.Role.PUSH_BUTTON: self._generate_push_button,
            Atspi.Role.RADIO_BUTTON: self._generate_radio_button,
            Atspi.Role.RADIO_MENU_ITEM: self._generate_radio_menu_item,
            "ROLE_REGION": self._generate_region,
            Atspi.Role.ROOT_PANE: self._generate_root_pane,
            Atspi.Role.ROW_HEADER: self._generate_row_header,
            Atspi.Role.SCROLL_BAR: self._generate_scroll_bar,
            Atspi.Role.SCROLL_PANE: self._generate_scroll_pane,
            Atspi.Role.SECTION: self._generate_section,
            Atspi.Role.SLIDER: self._generate_slider,
            Atspi.Role.SPIN_BUTTON: self._generate_spin_button,
            Atspi.Role.SEPARATOR: self._generate_separator,
            Atspi.Role.SPLIT_PANE: self._generate_split_pane,
            Atspi.Role.STATIC: self._generate_static,
            Atspi.Role.STATUS_BAR: self._generate_status_bar,
            Atspi.Role.SUBSCRIPT: self._generate_subscript,
            Atspi.Role.SUGGESTION: self._generate_suggestion,
            Atspi.Role.SUPERSCRIPT: self._generate_superscript,
            # TODO - JD: Replace this with the real role once dependencies are bumped to v2.56.
            "ROLE_SWITCH": self._generate_switch,
            Atspi.Role.TABLE: self._generate_table,
            Atspi.Role.TABLE_CELL: self._generate_table_cell_in_row,
            "REAL_ROLE_TABLE_CELL": self._generate_table_cell,
            Atspi.Role.TABLE_ROW: self._generate_table_row,
            Atspi.Role.TEAROFF_MENU_ITEM: self._generate_tearoff_menu_item,
            Atspi.Role.TERMINAL: self._generate_terminal,
            Atspi.Role.TEXT: self._generate_text,
            Atspi.Role.TOGGLE_BUTTON: self._generate_toggle_button,
            Atspi.Role.TOOL_BAR: self._generate_tool_bar,
            Atspi.Role.TOOL_TIP: self._generate_tool_tip,
            Atspi.Role.TREE: self._generate_tree,
            Atspi.Role.TREE_ITEM: self._generate_tree_item,
            Atspi.Role.WINDOW: self._generate_window,
        }

    @staticmethod
    def log_generator_output(func):
        """Decorator for logging."""

        def wrapper(*args, **kwargs):
            result = func(*args, **kwargs)
            tokens = [f"GENERATOR: {func.__name__}:", result]
            debug.print_tokens(debug.LEVEL_INFO, tokens, True)
            return result
        return wrapper

    @staticmethod
    def _clear_stored_data():
        """Clears any data we have cached for objects"""

        while True:
            time.sleep(2)
            msg = "GENERATOR: Clearing cache."
            debug.print_message(debug.LEVEL_INFO, msg, True)
            with Generator._lock:
                Generator.CACHED_DESCRIPTION = {}
                Generator.CACHED_IMAGE_DESCRIPTION = {}
                Generator.CACHED_IS_NAMELESS_TOGGLE = {}
                Generator.CACHED_NESTING_LEVEL = {}
                Generator.CACHED_STATIC_TEXT = {}
                Generator.CACHED_TEXT_SUBSTRING = {}
                Generator.CACHED_TEXT_LINE = {}
                Generator.CACHED_TEXT = {}
                Generator.CACHED_TEXT_EXPANDING_EOCS = {}
                Generator.CACHED_TREE_ITEM_LEVEL = {}
                Generator.USED_DESCRIPTION_FOR_NAME = {}
                Generator.USED_DESCRIPTION_FOR_STATIC_TEXT = {}

    @staticmethod
    def start_cache_clearing_thread():
        """Starts thread to periodically clear cached details."""

        thread = threading.Thread(target=Generator._clear_stored_data)
        thread.daemon = True
        thread.start()

    def _strings_are_redundant(self, str1, str2, threshold=0.7):
        if not (str1 and str2):
            return False

        if (str1 in str2 and len(str1.split()) > 3) or (str2 in str1 and len(str2.split()) > 3):
            msg = f"GENERATOR: Treating '{str2}' as redundant to '{str1}'"
            debug.print_message(debug.LEVEL_INFO, msg, True)
            return True

        similarity = round(SequenceMatcher(None, str1.lower(), str2.lower()).ratio(), 2)
        msg = (
            f"GENERATOR: Similarity between '{str1}', '{str2}': {similarity} "
            f"(threshold: {threshold})"
        )
        debug.print_message(debug.LEVEL_INFO, msg, True)
        return similarity >= threshold

    def generate_contents(self, _contents, **_args):
        """Returns presentation for a list of [obj, start, end, string]."""

        return []

    def generate_context(self, _obj, **_args):
        """Returns the presentation of the context of the object. Subclasses must override this."""

        return []

    def generate(self, obj, **args):
        """Returns the presentation of the object."""

        _generator = self._generators.get(args.get("role") or AXObject.get_role(obj))
        if _generator is None:
            tokens = [f"{self._mode.upper()} GENERATOR:", obj, "lacks dedicated generator"]
            debug.print_tokens(debug.LEVEL_INFO, tokens, True)
            _generator = self._generate_default_presentation

        if not args.get("formatType", None):
            if args.get('alreadyFocused', False):
                args["formatType"] = "focused"
            else:
                args["formatType"] = "unfocused"

        tokens = [f"{self._mode.upper()} GENERATOR:", _generator, "for", obj, "args:", args]
        debug.print_tokens(debug.LEVEL_INFO, tokens, True)

        result = _generator(obj, **args)
        tokens = [f"{self._mode.upper()} GENERATOR: Results:", result]
        debug.print_tokens(debug.LEVEL_INFO, tokens, True)

        if args.get("isProgressBarUpdate") and result and result[0]:
            self._set_progress_bar_update_time_and_value(obj)

        return result

    def get_localized_role_name(self, obj, **args):
        """Returns a string representing the localized rolename of obj."""

        result = AXUtilities.get_localized_role_name(obj, args.get("role"))
        return result

    def get_state_indicator(self, obj, **args):
        """Returns an array with the generated state of obj."""

        role = args.get("role", AXObject.get_role(obj))
        if AXUtilities.is_menu_item(obj, role):
            return self._generate_state_checked_if_checkable(obj, **args)
        if AXUtilities.is_radio_button(obj, role):
            return self._generate_state_selected_for_radio_button(obj, **args)
        if AXUtilities.is_radio_menu_item(obj, role):
            return self._generate_state_selected_for_radio_button(obj, **args)
        if AXUtilities.is_check_box(obj, role):
            return self._generate_state_checked(obj, **args)
        if AXUtilities.is_check_menu_item(obj, **args):
            return self._generate_state_checked(obj, **args)
        if AXUtilities.is_switch(obj, role):
            return self._generate_state_checked_for_switch(obj, **args)
        if AXUtilities.is_toggle_button(obj, role):
            return self._generate_state_pressed(obj, **args)
        if AXUtilities.is_table_cell(obj, role):
            return self._generate_state_checked_for_cell(obj, **args)
        return []

    def get_value(self, obj, **args):
        """Returns an array with the generated value."""

        role = args.get("role", AXObject.get_role(obj))
        if AXUtilities.is_progress_bar(obj, role):
            return self._generate_progress_bar_value(obj, **args)

        if AXUtilities.is_scroll_bar(obj, role) or AXUtilities.is_slider(obj, role):
            return self._generate_value_as_percentage(obj, **args)

        return []

    def _generate_result_separator(self, _obj, **_args):
        return []

    ################################# BASIC DETAILS #################################

    @log_generator_output
    def _generate_accessible_description(self, obj, **args):
        if args.get("omitDescription"):
            return []

        if hash(obj) in Generator.CACHED_DESCRIPTION:
            return Generator.CACHED_DESCRIPTION.get(hash(obj))

        if Generator.USED_DESCRIPTION_FOR_STATIC_TEXT.get(hash(obj)):
            Generator.CACHED_DESCRIPTION[hash(obj)] = []
            return []

        if Generator.USED_DESCRIPTION_FOR_NAME.get(hash(obj)):
            Generator.CACHED_DESCRIPTION[hash(obj)] = []
            return []

        description = AXObject.get_description(obj) or AXUtilities.get_displayed_description(obj)
        if not description:
            Generator.CACHED_DESCRIPTION[hash(obj)] = []
            return []

        if self._strings_are_redundant(AXObject.get_name(obj), description):
            Generator.CACHED_DESCRIPTION[hash(obj)] = []
            return []

        focus = focus_manager.get_manager().get_locus_of_focus()
        if focus and obj != focus \
           and description in [AXObject.get_name(focus), AXObject.get_description(focus)]:
            Generator.CACHED_DESCRIPTION[hash(obj)] = []
            return []

        Generator.CACHED_DESCRIPTION[hash(obj)] = [description]
        return [description]

    @log_generator_output
    def _generate_accessible_image_description(self, obj, **_args):
        if hash(obj) in Generator.CACHED_IMAGE_DESCRIPTION:
            return Generator.CACHED_IMAGE_DESCRIPTION.get(hash(obj))

        description = AXObject.get_image_description(obj)
        if not description:
            Generator.CACHED_IMAGE_DESCRIPTION[hash(obj)] = []
            return []

        Generator.CACHED_IMAGE_DESCRIPTION[hash(obj)] = [description]
        return [description]

    @log_generator_output
    def _generate_accessible_label(self, obj, **_args):
        result = []
        label = AXUtilities.get_displayed_label(obj)
        if label:
            result.append(label)
        return result

    @log_generator_output
    def _generate_accessible_label_and_name(self, obj, **args):
        focus = focus_manager.get_manager().get_locus_of_focus()
        # TODO - JD: The role check is a quick workaround for issue #535 in which we stopped
        # presenting Qt table cells because Qt keeps giving us a different object each and
        # every time we ask for the cell. https://bugreports.qt.io/browse/QTBUG-128558
        # Once that's fixed we can remove the role check.
        if focus and obj != focus and AXObject.get_role(obj) != AXObject.get_role(focus):
            name = AXObject.get_name(obj) or AXObject.get_description(obj)
            if name and name in [AXObject.get_name(focus), AXObject.get_description(focus)]:
                return []

        result = []
        label = self._generate_accessible_label(obj, **args)
        name = self._generate_accessible_name(obj, **args)
        role = args.get("role", AXObject.get_role(obj))
        if not (label or name) and role == Atspi.Role.TABLE_CELL:
            descendant = self._script.utilities.realActiveDescendant(obj)
            name = self._generate_accessible_name(descendant)

        # If we don't have a label, always use the name.
        if not label:
            return name

        result.extend(label)
        if not name:
            return result

        if self._strings_are_redundant(name[0], label[0]):
            if len(name[0]) < len(label[0]):
                return label
            return name

        result.extend(name)
        if result:
            return result

        parent = AXObject.get_parent(obj)
        if AXUtilities.is_autocomplete(parent):
            result = self._generate_accessible_label_and_name(parent, **args)

        return result

    @log_generator_output
    def _generate_accessible_name(self, obj, **args):
        Generator.USED_DESCRIPTION_FOR_NAME[hash(obj)] = False
        name = AXObject.get_name(obj)
        if name:
            return [name]

        description = AXObject.get_description(obj)
        if description:
            Generator.USED_DESCRIPTION_FOR_NAME[hash(obj)] = True
            return [description]

        link = None
        parent = AXObject.get_parent(obj)
        if AXUtilities.is_link(obj, args.get("role")):
            link = obj
        elif AXUtilities.is_link(parent):
            link = parent
        if link:
            basename = AXHypertext.get_link_basename(link, remove_extension=True)
            if basename:
                return [basename]

        # To make the unlabeled icons in gnome-panel more accessible.
        if AXUtilities.is_icon(obj) and AXUtilities.is_panel(parent):
            return self._generate_accessible_name(parent)

        return []

    @log_generator_output
    def _generate_accessible_placeholder_text(self, obj, **_args):
        attrs = AXObject.get_attributes_dict(obj)
        placeholder = attrs.get("placeholder-text")
        if placeholder and placeholder != AXObject.get_name(obj):
            return [placeholder]

        placeholder = attrs.get("placeholder")
        if placeholder and placeholder != AXObject.get_name(obj):
            return [placeholder]

        return []

    @log_generator_output
    def _generate_accessible_role(self, _obj, **_args):
        return []

    @log_generator_output
    def _generate_accessible_static_text(self, obj, **args):
        if hash(obj) in Generator.CACHED_STATIC_TEXT:
            return Generator.CACHED_STATIC_TEXT.get(hash(obj))

        result = self._generate_accessible_description(obj, **args)
        Generator.USED_DESCRIPTION_FOR_STATIC_TEXT[hash(obj)] = bool(result)
        if result:
            Generator.CACHED_STATIC_TEXT[hash(obj)] = result
            return result

        if args.get("formatType") != "ancestor":
            result = self._generate_text_expanding_embedded_objects(obj, **args)
            if result:
                Generator.CACHED_STATIC_TEXT[hash(obj)] = result
                return result

        labels = self._script.utilities.unrelatedLabels(obj)
        for label in labels:
            result.extend(self._generate_accessible_name(label, **args))

        Generator.CACHED_STATIC_TEXT[hash(obj)] = result
        return result

    @log_generator_output
    def _get_functional_role(self, obj, **args):
        role = args.get("role", AXObject.get_role(obj))
        if AXUtilities.is_math_related(obj):
            if AXUtilities.is_math_sub_or_super_script(obj):
                return "ROLE_MATH_SCRIPT_SUBSUPER"
            if AXUtilities.is_math_under_or_over_script(obj):
                return "ROLE_MATH_SCRIPT_UNDEROVER"
            if AXUtilities.is_math_multi_script(obj):
                return "ROLE_MATH_MULTISCRIPT"
            if AXUtilities.is_math_enclose(obj):
                return "ROLE_MATH_ENCLOSED"
            if AXUtilities.is_math_fenced(obj):
                return "ROLE_MATH_FENCED"
            if AXUtilities.is_math_table(obj):
                return "ROLE_MATH_TABLE"
            if AXUtilities.is_math_table_row(obj):
                return "ROLE_MATH_TABLE_ROW"
        if AXUtilities.is_dpub(obj, role):
            if AXUtilities.is_landmark(obj):
                return "ROLE_DPUB_LANDMARK"
            if AXUtilities.is_section(obj, role):
                return "ROLE_DPUB_SECTION"
        if AXUtilities.is_switch(obj, role):
            return "ROLE_SWITCH"
        if self._script.utilities.isAnchor(obj):
            return Atspi.Role.STATIC
        if AXUtilities.is_block_quote(obj, role):
            return Atspi.Role.BLOCK_QUOTE
        if AXUtilities.is_comment(obj, role):
            return Atspi.Role.COMMENT
        if self._script.utilities.isContentError(obj):
            return "ROLE_CONTENT_ERROR"
        if AXUtilities.is_description_list(obj, role):
            return Atspi.Role.DESCRIPTION_LIST
        if AXUtilities.is_description_term(obj, role):
            return Atspi.Role.DESCRIPTION_TERM
        if AXUtilities.is_description_value(obj, role):
            return Atspi.Role.DESCRIPTION_VALUE
        if AXUtilities.is_feed_article(obj, role):
            return "ROLE_ARTICLE_IN_FEED"
        if AXUtilities.is_feed(obj, role):
            return "ROLE_FEED"
        if AXUtilities.is_landmark(obj, role):
            if AXUtilities.is_landmark_region(obj):
                return "ROLE_REGION"
            return Atspi.Role.LANDMARK
        if self._script.utilities.isDocument(obj) and AXObject.supports_image(obj):
            return Atspi.Role.IMAGE

        return role

    @log_generator_output
    def _generate_descendants(self, obj, **args):
        result = []
        obj_name = AXObject.get_name(obj) or AXUtilities.get_displayed_label(obj)
        obj_desc = AXObject.get_description(obj) or AXUtilities.get_displayed_description(obj)
        descendants = self._script.utilities.getOnScreenObjects(obj)
        used_description_as_static_text = False
        for child in descendants:
            if child == obj:
                continue
            if AXUtilities.is_section(child):
                continue
            if AXUtilities.is_paragraph(child):
                continue
            if AXUtilities.is_table_related(child):
                continue
            if AXUtilities.is_static(child):
                continue
            if AXUtilities.is_link(child):
                continue
            if AXUtilities.is_image(child):
                continue
            if AXUtilities.is_separator(child):
                continue

            child_name = AXObject.get_name(child)
            if AXUtilities.is_button(child):
                if child_name in obj_name:
                    continue
                if AXUtilities.has_popup(child):
                    continue

            if AXUtilities.is_label(child):
                if not AXText.has_presentable_text(child):
                    continue
                if AXUtilities.get_is_label_for(obj):
                    continue
                if self._strings_are_redundant(obj_name, child_name):
                    continue
                if self._strings_are_redundant(obj_desc, child_name):
                    used_description_as_static_text = True

            child_result = self.generate(child, includeContext=False, omitDescription=True)
            if child_result:
                result.extend(child_result)
                result.extend(self._generate_result_separator(child, **args))

        Generator.USED_DESCRIPTION_FOR_STATIC_TEXT[hash(obj)] = used_description_as_static_text
        return result

    @log_generator_output
    def _generate_focused_item(self, obj, **args):
        role = args.get("role")
        if not (AXUtilities.is_list(obj, role) or AXUtilities.is_list_box(obj, role)):
            return []

        if AXObject.supports_selection(obj):
            items = self._script.utilities.selectedChildren(obj)
        else:
            items = [AXUtilities.get_focused_object(obj)]
        if not (items and items[0]):
            return []

        result = []
        for item in map(self._generate_accessible_name, items):
            result.extend(item)

        return result

    @log_generator_output
    def _generate_radio_button_group(self, obj, **_args):
        if not AXUtilities.is_radio_button(obj):
            return []

        radio_group_label = None
        labels = AXUtilities.get_is_labelled_by(obj, False)
        if labels:
            radio_group_label = labels[0]
        if radio_group_label:
            name = AXObject.get_name(radio_group_label)
            if name and name != AXObject.get_name(obj):
                return [name]

        parent = AXObject.get_parent_checked(obj)
        while parent:
            if AXUtilities.is_list(parent):
                break
            if AXUtilities.is_panel(parent) or AXUtilities.is_filler(parent):
                label = self._generate_accessible_label_and_name(parent)
                if label:
                    return label
            parent = AXObject.get_parent_checked(parent)
        return []

    ##################################### STATE #####################################

    @log_generator_output
    def _generate_state_checked(self, obj, **_args):
        if self._mode == "braille":
            indicators = object_properties.CHECK_BOX_INDICATORS_BRAILLE
        elif self._mode == "speech":
            indicators = object_properties.CHECK_BOX_INDICATORS_SPEECH
        elif self._mode == "sound":
            indicators = object_properties.CHECK_BOX_INDICATORS_SOUND
        else:
            return []

        if AXUtilities.is_checked(obj):
            return [indicators[1]]
        if AXUtilities.is_indeterminate(obj):
            return [indicators[2]]
        return [indicators[0]]

    @log_generator_output
    def _generate_state_checked_for_cell(self, obj, **args):
        result = []
        if self._script.utilities.hasMeaningfulToggleAction(obj):
            args["role"] = Atspi.Role.CHECK_BOX
            result.extend(self.generate(obj, **args))

        return result

    @log_generator_output
    def _generate_state_checked_for_switch(self, obj, **_args):
        if self._mode == "braille":
            indicators = object_properties.SWITCH_INDICATORS_BRAILLE
        elif self._mode == "speech":
            indicators = object_properties.SWITCH_INDICATORS_SPEECH
        elif self._mode == "sound":
            indicators = object_properties.SWITCH_INDICATORS_SOUND
        else:
            return []

        if AXUtilities.is_checked(obj) or AXUtilities.is_pressed(obj):
            return [indicators[1]]
        return [indicators[0]]

    @log_generator_output
    def _generate_state_checked_if_checkable(self, obj, **args):
        if AXUtilities.is_checkable(obj) or AXUtilities.is_check_menu_item(obj):
            return self._generate_state_checked(obj, **args)

        if AXUtilities.is_checked(obj):
            return self._generate_state_checked(obj, **args)

        return []

    @log_generator_output
    def _generate_state_expanded(self, obj, **_args):
        if self._mode == "braille":
            indicators = object_properties.EXPANSION_INDICATORS_BRAILLE
        elif self._mode == "speech":
            indicators = object_properties.EXPANSION_INDICATORS_SPEECH
        elif self._mode == "sound":
            indicators = object_properties.EXPANSION_INDICATORS_SOUND
        else:
            return []

        if AXUtilities.is_collapsed(obj):
            return [indicators[0]]
        if AXUtilities.is_expanded(obj):
            return [indicators[1]]
        if AXUtilities.is_expandable(obj):
            return [indicators[0]]
        return []

    @log_generator_output
    def _generate_state_has_popup(self, _obj, **_args):
        return []

    @log_generator_output
    def _generate_state_invalid(self, obj, **_args):
        error = self._script.utilities.getError(obj)
        if not error:
            return []

        if self._mode == "braille":
            indicators = object_properties.INVALID_INDICATORS_BRAILLE
        elif self._mode == "speech":
            indicators = object_properties.INVALID_INDICATORS_SPEECH
        elif self._mode == "sound":
            indicators = object_properties.INVALID_INDICATORS_SOUND
        else:
            return []

        result = []
        if error == 'spelling':
            indicator = indicators[1]
        elif error == 'grammar':
            indicator = indicators[2]
        else:
            indicator = indicators[0]

        error_message = self._script.utilities.getErrorMessage(obj)
        if error_message:
            result.append(f"{indicator}: {error_message}")
        else:
            result.append(indicator)

        return result

    @log_generator_output
    def _generate_state_multiselectable(self, obj, **_args):
        if not (AXUtilities.is_multiselectable(obj) and AXObject.get_child_count(obj)):
            return []

        # TODO - JD: There is no braille property and the braille generation
        # doesn't generate this state. Shouldn't it be presented in braille?

        if self._mode == "speech":
            return [object_properties.STATE_MULTISELECT_SPEECH]
        if self._mode == "sound":
            return [object_properties.STATE_MULTISELECT_SOUND]
        return []

    @log_generator_output
    def _generate_state_pressed(self, obj, **_args):
        if self._mode == "braille":
            indicators = object_properties.TOGGLE_BUTTON_INDICATORS_BRAILLE
        elif self._mode == "speech":
            indicators = object_properties.TOGGLE_BUTTON_INDICATORS_SPEECH
        elif self._mode == "sound":
            indicators = object_properties.TOGGLE_BUTTON_INDICATORS_SOUND
        else:
            return []

        if AXUtilities.is_checked(obj) or AXUtilities.is_pressed(obj):
            return [indicators[1]]
        return [indicators[0]]

    @log_generator_output
    def _generate_state_read_only(self, obj, **_args):
        if not AXUtilities.is_read_only(obj):
            return []

        if self._mode == "braille":
            return [object_properties.STATE_READ_ONLY_BRAILLE]
        if self._mode == "speech":
            return [object_properties.STATE_READ_ONLY_SPEECH]
        if self._mode == "sound":
            return [object_properties.STATE_READ_ONLY_SOUND]

        return []

    @log_generator_output
    def _generate_state_required(self, obj, **_args):
        is_required = AXUtilities.is_required(obj)
        if not is_required and AXUtilities.is_radio_button(obj):
            is_required = AXUtilities.is_required(AXObject.get_parent(obj))
        if not is_required:
            return []

        if self._mode == "braille":
            return [object_properties.STATE_REQUIRED_BRAILLE]
        if self._mode == "speech":
            return [object_properties.STATE_REQUIRED_SPEECH]
        if self._mode == "sound":
            return [object_properties.STATE_REQUIRED_SOUND]

        return []

    @log_generator_output
    def _generate_state_selected_for_radio_button(self, obj, **_args):
        if self._mode == "braille":
            indicators = object_properties.RADIO_BUTTON_INDICATORS_BRAILLE
        elif self._mode == "speech":
            indicators = object_properties.RADIO_BUTTON_INDICATORS_SPEECH
        elif self._mode == "sound":
            indicators = object_properties.RADIO_BUTTON_INDICATORS_SOUND
        else:
            return []

        if AXUtilities.is_checked(obj):
            return [indicators[1]]
        return [indicators[0]]

    @log_generator_output
    def _generate_state_sensitive(self, obj, **_args):
        if AXUtilities.is_sensitive(obj):
            return []

        if self._script.utilities.isSpreadSheetCell(obj):
            return []

        if self._mode == "braille":
            return [object_properties.STATE_INSENSITIVE_BRAILLE]
        if self._mode == "speech":
            return [object_properties.STATE_INSENSITIVE_SPEECH]
        if self._mode == "sound":
            return [object_properties.STATE_INSENSITIVE_SOUND]

        return []

    @log_generator_output
    def _generate_state_unselected(self, _obj, **_args):
        return []

    @log_generator_output
    def _generate_state_visited(self, _obj, **_args):
        # Note that in the case of speech, this state is added to the role name.
        return []

    ##################################### TEXT ######################################

    @log_generator_output
    def _generate_text_substring(self, obj, **args):
        start = args.get("startOffset")
        end = args.get("endOffset")
        if (hash(obj), start, end) in Generator.CACHED_TEXT_SUBSTRING:
            return Generator.CACHED_TEXT_SUBSTRING.get((hash(obj), start, end))

        if start is None or end is None:
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT_SUBSTRING[(hash(obj), start, end)] = []
            return []

        substring = args.get("string", AXText.get_substring(obj, start, end))
        if self._script.EMBEDDED_OBJECT_CHARACTER not in substring:
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT_SUBSTRING[(hash(obj), start, end)] = [substring]
            return [substring]

        if not AXUtilities.is_editable(obj):
            Generator.CACHED_TEXT_SUBSTRING[(hash(obj), start, end)] = []
        return []

    @log_generator_output
    def _generate_text_line(self, obj, **args):
        start = args.get("startOffset")
        end = args.get("endOffset")
        if (hash(obj), start, end) in Generator.CACHED_TEXT_LINE:
            return Generator.CACHED_TEXT_LINE.get((hash(obj), start, end))

        result = Generator._generate_text_substring(self, obj, **args)
        if result:
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT_LINE[(hash(obj), start, end)] = result
            return result

        text = AXText.get_line_at_offset(obj)[0]
        if text and self._script.EMBEDDED_OBJECT_CHARACTER not in text:
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT_LINE[(hash(obj), start, end)] = [text]
            return [text]

        if not AXUtilities.is_editable(obj):
            Generator.CACHED_TEXT_LINE[(hash(obj), start, end)] = []
        return []

    @log_generator_output
    def _generate_text_content(self, obj, **args):
        if hash(obj) in Generator.CACHED_TEXT:
            return Generator.CACHED_TEXT.get(hash(obj))

        result = Generator._generate_text_substring(self, obj, **args)
        if result:
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT[hash(obj)] = result
            return result

        text = AXText.get_all_text(obj)
        if text and self._script.EMBEDDED_OBJECT_CHARACTER not in text:
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT[hash(obj)] = [text]
            return [text]

        if not AXUtilities.is_editable(obj):
            Generator.CACHED_TEXT[hash(obj)] = []
        return []

    @log_generator_output
    def _generate_text_expanding_embedded_objects(self, obj, **args):
        start = args.get("startOffset")
        end = args.get("endOffset")
        if (hash(obj), start, end) in Generator.CACHED_TEXT_EXPANDING_EOCS:
            return Generator.CACHED_TEXT_EXPANDING_EOCS.get((hash(obj), start, end))

        text = self._script.utilities.expandEOCs(
            obj, args.get("startOffset", 0), args.get("endOffset", -1))
        if text.strip() and self._script.EMBEDDED_OBJECT_CHARACTER not in text \
           and not self._strings_are_redundant(AXObject.get_name(obj), text):
            if not AXUtilities.is_editable(obj):
                Generator.CACHED_TEXT_EXPANDING_EOCS[hash(obj), start, end] = [text]
            return [text]

        if not AXUtilities.is_editable(obj):
            Generator.CACHED_TEXT_EXPANDING_EOCS[(hash(obj), start, end)] = []
        return []

    ################################## POSITION #####################################

    @log_generator_output
    def _get_nesting_level(self, obj):
        level = Generator.CACHED_NESTING_LEVEL.get(hash(obj))
        if level is None:
            level = self._script.utilities.nestingLevel(obj)
            Generator.CACHED_NESTING_LEVEL[hash(obj)] = level
        return level

    @log_generator_output
    def _generate_nesting_level(self, obj, **args):
        if args.get("startOffset") is not None and args.get("endOffset") is not None:
            return []

        level = self._get_nesting_level(obj)
        if not level:
            return []

        if self._mode == "braille":
            return [object_properties.NESTING_LEVEL_BRAILLE % (level)]
        if self._mode == "speech":
            return [object_properties.NESTING_LEVEL_SPEECH % (level)]
        return []

    @log_generator_output
    def _generate_position_in_list(self, _obj, **_args):
        return []

    @log_generator_output
    def _generate_tree_item_level(self, obj, **args):
        level = Generator.CACHED_TREE_ITEM_LEVEL.get(hash(obj))
        if level is None:
            level = self._script.utilities.nodeLevel(obj)
            Generator.CACHED_TREE_ITEM_LEVEL[hash(obj)] = level

        if level < 0:
            return []

        prior_object = args.get("priorObj")
        if args.get("newOnly") and prior_object:
            old_level = Generator.CACHED_TREE_ITEM_LEVEL.get(hash(prior_object))
            if old_level is None:
                old_level = self._script.utilities.nodeLevel(prior_object)
                Generator.CACHED_TREE_ITEM_LEVEL[hash(prior_object)] = old_level
            if old_level == level:
                return []

        if self._mode == "braille":
            return [object_properties.NODE_LEVEL_BRAILLE % (level + 1)]
        if self._mode == "speech":
            return [object_properties.NODE_LEVEL_SPEECH % (level + 1)]
        return []

    ################################ PROGRESS BARS ##################################

    @log_generator_output
    def _generate_progress_bar_index(self, _obj, **_args):
        return []

    @log_generator_output
    def _generate_progress_bar_value(self, _obj, **_args):
        return []

    def _get_progress_bar_update_interval(self):
        return int(settings_manager.get_manager().get_setting('progressBarUpdateInterval'))

    def _should_present_progress_bar_update(self, obj, **_args):
        percent = AXValue.get_value_as_percent(obj)
        last_time, last_value = self._get_progress_bar_update_time_and_value(obj, type=self)
        if percent == last_value:
            tokens = ["GENERATOR: Not presenting update for", obj, ". Value still", percent]
            debug.print_tokens(debug.LEVEL_INFO, tokens, True)
            return False

        if percent == 100:
            return True

        interval = int(time.time() - last_time)
        return interval >= self._get_progress_bar_update_interval()

    def _clean_up_cached_progress_bars(self):
        bars = list(filter(AXObject.is_valid, self._active_progress_bars))
        self._active_progress_bars = {x:self._active_progress_bars.get(x) for x in bars}

    def _get_most_recent_progress_bar_update(self):
        self._clean_up_cached_progress_bars()
        if not self._active_progress_bars.values():
            return None, 0.0, None

        sorted_values = sorted(self._active_progress_bars.values(), key=lambda x: x[0])
        prev_time, prev_value = sorted_values[-1]
        return list(self._active_progress_bars.keys())[-1], prev_time, prev_value

    def _get_progress_bar_number_and_count(self, obj):
        self._clean_up_cached_progress_bars()
        if obj not in self._active_progress_bars:
            self._active_progress_bars[obj] = 0.0, None

        this_value = self._get_progress_bar_update_time_and_value(obj)
        index = list(self._active_progress_bars.values()).index(this_value)
        return index + 1, len(self._active_progress_bars)

    def _get_progress_bar_update_time_and_value(self, obj, **_args):
        if obj not in self._active_progress_bars:
            self._active_progress_bars[obj] = 0.0, None

        return self._active_progress_bars.get(obj)

    def _set_progress_bar_update_time_and_value(self, obj, last_time=None, last_value=None):
        last_time = last_time or time.time()
        last_value = last_value or AXValue.get_value_as_percent(obj)
        self._active_progress_bars[obj] = last_time, last_value

    ##################################### TABLE #####################################

    # TODO - JD: This function and fake role really need to die....
    @log_generator_output
    def _generate_real_table_cell(self, obj, **args):
        result = []
        args["role"] = "REAL_ROLE_TABLE_CELL"
        result.extend(self.generate(obj, **args))
        return result

    def _get_is_nameless_toggle(self, obj):
        if hash(obj) in Generator.CACHED_IS_NAMELESS_TOGGLE:
            return Generator.CACHED_IS_NAMELESS_TOGGLE[hash(obj)]

        if not self._script.utilities.hasMeaningfulToggleAction(obj):
            Generator.CACHED_IS_NAMELESS_TOGGLE[hash(obj)] = False
            return False

        descendant = self._script.utilities.realActiveDescendant(obj)
        if AXObject.get_name(descendant) or AXText.get_all_text(descendant):
            Generator.CACHED_IS_NAMELESS_TOGGLE[hash(obj)] = False
            return False

        Generator.CACHED_IS_NAMELESS_TOGGLE[hash(obj)] = True
        return True

    # TODO - JD: This is part of the complicated "REAL_ROLE_TABLE_CELL" mess.
    @log_generator_output
    def _generate_table_cell_row(self, obj, **args):
        present_all = args.get("readingRow") is True \
            or args.get("formatType") == "detailedWhereAmI" \
            or self._script.utilities.shouldReadFullRow(obj, args.get("priorObj"))

        if not present_all:
            return self._generate_real_table_cell(obj, **args)

        args["readingRow"] = True
        result = []
        cells = self._script.utilities.getShowingCellsInSameRow(
            obj, forceFullRow=not self._script.utilities.isSpreadSheetCell(obj))

        row = AXObject.find_ancestor(obj, AXUtilities.is_table_row)
        if row and AXObject.get_name(row) and not AXUtilities.is_layout_only(row):
            return self.generate(row)

        # Remove any pre-calculated values which only apply to obj and not row cells.
        do_not_include = ["startOffset", "endOffset", "string"]
        other_cell_args = args.copy()
        for arg in do_not_include:
            other_cell_args.pop(arg, None)

        for cell in cells:
            if cell == obj:
                cell_result = self._generate_real_table_cell(cell, **args)
            else:
                cell_result = self._generate_real_table_cell(cell, **other_cell_args)
            if cell_result and result and self._mode == "braille":
                result.append(braille.Region(object_properties.TABLE_CELL_DELIMITER_BRAILLE))
            result.extend(cell_result)

        result.extend(self._generate_position_in_list(obj, **args))
        return result


    # TODO - JD: If we had dedicated generators for cell types, we wouldn't need this.
    @log_generator_output
    def _generate_column_header_if_toggle_and_no_text(self, obj, **_args):
        if not self._get_is_nameless_toggle(obj):
            return []

        result = []
        headers = AXTable.get_column_headers(obj)
        if headers:
            result.append(AXObject.get_name(headers[0]) or AXText.get_all_text(headers[0]))

        return result

    # TODO - JD: This needs to also be looked into.
    @log_generator_output
    def _generate_real_active_descendant_displayed_text(self, obj, **args):
        rad = self._script.utilities.realActiveDescendant(obj)

        if not (AXUtilities.is_table_cell(rad) and AXObject.get_child_count(rad)):
            return self._generate_text_content(rad, **args)

        content = {AXObject.get_name(x) for x in AXObject.iter_children(rad)}
        rv = " ".join(filter(lambda x: x, content))
        if not rv:
            return self._generate_text_content(rad, **args)
        return [rv]

    @log_generator_output
    def _generate_table_cell_column_header(self, obj, **args):
        if args.get("readingRow") and not self._get_is_nameless_toggle(obj):
            return []

        result = []
        if args.get("newOnly"):
            headers = AXTable.get_new_column_headers(obj, args.get("priorObj"))
        else:
            headers = AXTable.get_column_headers(obj)

        tokens = []
        for header in headers:
            token = AXObject.get_name(header).strip() or AXText.get_all_text(header).strip()
            if token:
                tokens.append(token)

        if not tokens:
            return result

        text = ". ".join(tokens)
        if not self._get_is_nameless_toggle(obj):
            role_string = self.get_localized_role_name(obj, role=Atspi.Role.COLUMN_HEADER)
            if self._mode == "speech":
                if settings.speechVerbosityLevel == settings.VERBOSITY_LEVEL_VERBOSE \
                and args.get("formatType") not in ["basicWhereAmI", "detailedWhereAmI"]:
                    text = f"{text} {role_string}"
            elif self._mode == "braille":
                text = f"{text} {role_string}"

        result.append(text)
        return result

    @log_generator_output
    def _generate_table_cell_row_header(self, obj, **args):
        if args.get("readingRow"):
            return []

        result = []
        if args.get("newOnly"):
            headers = AXTable.get_new_row_headers(obj, args.get("priorObj"))
        else:
            headers = AXTable.get_row_headers(obj)

        tokens = []
        for header in headers:
            token = AXObject.get_name(header).strip() or AXText.get_all_text(header).strip()
            if token:
                tokens.append(token)

        if not tokens:
            return result

        text = ". ".join(tokens)
        role_string =  self.get_localized_role_name(obj, role=Atspi.Role.ROW_HEADER)
        if self._mode == "speech":
            if settings.speechVerbosityLevel == settings.VERBOSITY_LEVEL_VERBOSE \
               and args.get("formatType") not in ["basicWhereAmI", "detailedWhereAmI"]:
                text = f"{text} {role_string}"
        elif self._mode == "braille":
            text = f"{text} {role_string}"

        result.append(text)
        return result

    @log_generator_output
    def _generate_table_sort_order(self, obj, **_args):
        description = AXTable.get_presentable_sort_order_from_header(obj)
        if not description:
            return []

        return [description]

    ##################################### VALUE #####################################

    @log_generator_output
    def _generate_value(self, obj, **args):
        if AXUtilities.is_combo_box(obj, args.get("role")):
            value = self._script.utilities.getComboBoxValue(obj)
            return [value]

        if AXUtilities.is_separator(obj, args.get("role")) and not AXUtilities.is_focused(obj):
            return []

        result = AXValue.get_current_value_text(obj)
        if result:
            return [result]
        return []

    @log_generator_output
    def _generate_value_as_percentage(self, obj, **_args):
        percent = AXValue.get_value_as_percent(obj)
        if percent is not None:
            return [f'{percent}%']

        return []

    ################################### PER-ROLE ###################################

    def _generate_default_presentation(self, _obj, **_args):
        """Provides a default/role-agnostic presentation of obj."""

        return []

    def _generate_accelerator_label(self, _obj, **_args):
        """Generates presentation for the accelerator-label role."""

        return []

    def _generate_alert(self, _obj, **_args):
        """Generates presentation for the alert role."""

        return []

    def _generate_animation(self, _obj, **_args):
        """Generates presentation for the animation role."""

        return []

    def _generate_application(self, _obj, **_args):
        """Generates presentation for the application role."""

        return []

    def _generate_arrow(self, _obj, **_args):
        """Generates presentation for the arrow role."""

        return []

    def _generate_article(self, _obj, **_args):
        """Generates presentation for the article role."""

        return []

    def _generate_article_in_feed(self, _obj, **_args):
        """Generates presentation for the article role when the article is in a feed."""

        return []

    def _generate_audio(self, _obj, **_args):
        """Generates presentation for the audio role."""

        return []

    def _generate_autocomplete(self, _obj, **_args):
        """Generates presentation for the autocomplete role."""

        return []

    def _generate_block_quote(self, _obj, **_args):
        """Generates presentation for the block-quote role."""

        return []

    def _generate_calendar(self, _obj, **_args):
        """Generates presentation for the calendar role."""

        return []

    def _generate_canvas(self, _obj, **_args):
        """Generates presentation for the canvas role."""

        return []

    def _generate_caption(self, _obj, **_args):
        """Generates presentation for the caption role."""

        return []

    def _generate_chart(self, _obj, **_args):
        """Generates presentation for the chart role."""

        return []

    def _generate_check_box(self, _obj, **_args):
        """Generates presentation for the check-box role."""

        return []

    def _generate_check_menu_item(self, _obj, **_args):
        """Generates presentation for the check-menu-item role."""

        return []

    def _generate_color_chooser(self, _obj, **_args):
        """Generates presentation for the color-chooser role."""

        return []

    def _generate_column_header(self, _obj, **_args):
        """Generates presentation for the column-header role."""

        return []

    def _generate_combo_box(self, _obj, **_args):
        """Generates presentation for the combo-box role."""

        return []

    def _generate_comment(self, _obj, **_args):
        """Generates presentation for the comment role."""

        return []

    def _generate_content_deletion(self, _obj, **_args):
        """Generates presentation for the content-deletion role."""

        return []

    def _generate_content_error(self, _obj, **_args):
        """Generates presentation for a role with a content-related error."""

        return []

    def _generate_content_insertion(self, _obj, **_args):
        """Generates presentation for the content-insertion role."""

        return []

    def _generate_date_editor(self, _obj, **_args):
        """Generates presentation for the date-editor role."""

        return []

    def _generate_definition(self, _obj, **_args):
        """Generates presentation for the definition role."""

        return []

    def _generate_description_list(self, _obj, **_args):
        """Generates presentation for the description-list role."""

        return []

    def _generate_description_term(self, _obj, **_args):
        """Generates presentation for the description-term role."""

        return []

    def _generate_description_value(self, _obj, **_args):
        """Generates presentation for the description-value role."""

        return []

    def _generate_desktop_frame(self, _obj, **_args):
        """Generates presentation for the desktop-frame role."""

        return []

    def _generate_desktop_icon(self, _obj, **_args):
        """Generates presentation for the desktop-icon role."""

        return []

    def _generate_dial(self, _obj, **_args):
        """Generates presentation for the dial role."""

        return []

    def _generate_dialog(self, _obj, **_args):
        """Generates presentation for the dialog role."""

        return []

    def _generate_directory_pane(self, _obj, **_args):
        """Generates presentation for the directory_pane role."""

        return []

    def _generate_document(self, _obj, **_args):
        """Generates presentation for document-related roles."""

        return []

    def _generate_document_email(self, _obj, **_args):
        """Generates presentation for the document-email role."""

        return []

    def _generate_document_frame(self, _obj, **_args):
        """Generates presentation for the document-frame role."""

        return []

    def _generate_document_presentation(self, _obj, **_args):
        """Generates presentation for the document-presentation role."""

        return []

    def _generate_document_spreadsheet(self, _obj, **_args):
        """Generates presentation for the document-spreadsheet role."""

        return []

    def _generate_document_text(self, _obj, **_args):
        """Generates presentation for the document-text role."""

        return []

    def _generate_document_web(self, _obj, **_args):
        """Generates presentation for the document-web role."""

        return []

    def _generate_dpub_landmark(self, _obj, **_args):
        """Generates presentation for the dpub section role."""

        return []

    def _generate_dpub_section(self, _obj, **_args):
        """Generates presentation for the dpub section role."""

        return []

    def _generate_drawing_area(self, _obj, **_args):
        """Generates presentation for the drawing-area role."""

        return []

    def _generate_editbar(self, _obj, **_args):
        """Generates presentation for the editbar role."""

        return []

    def _generate_embedded(self, _obj, **_args):
        """Generates presentation for the embedded role."""

        return []

    def _generate_entry(self, _obj, **_args):
        """Generates presentation for the entry role."""

        return []

    def _generate_feed(self, _obj, **_args):
        """Generates presentation for the feed role."""

        return []

    def _generate_file_chooser(self, _obj, **_args):
        """Generates presentation for the file-chooser role."""

        return []

    def _generate_filler(self, _obj, **_args):
        """Generates presentation for the filler role."""

        return []

    def _generate_font_chooser(self, _obj, **_args):
        """Generates presentation for the font-chooser role."""

        return []

    def _generate_footer(self, _obj, **_args):
        """Generates presentation for the footer role."""

        return []

    def _generate_footnote(self, _obj, **_args):
        """Generates presentation for the footnote role."""

        return []

    def _generate_form(self, _obj, **_args):
        """Generates presentation for the form role."""

        return []

    def _generate_frame(self, _obj, **_args):
        """Generates presentation for the frame role."""

        return []

    def _generate_glass_pane(self, _obj, **_args):
        """Generates presentation for the glass-pane role."""

        return []

    def _generate_grouping(self, _obj, **_args):
        """Generates presentation for the grouping role."""

        return []

    def _generate_header(self, _obj, **_args):
        """Generates presentation for the header role."""

        return []

    def _generate_heading(self, _obj, **_args):
        """Generates presentation for the heading role."""

        return []

    def _generate_html_container(self, _obj, **_args):
        """Generates presentation for the html-container role."""

        return []

    def _generate_icon(self, _obj, **_args):
        """Generates presentation for the icon role."""

        return []

    def _generate_image(self, _obj, **_args):
        """Generates presentation for the image role."""

        return []

    def _generate_image_map(self, _obj, **_args):
        """Generates presentation for the image-map role."""

        return []

    def _generate_info_bar(self, _obj, **_args):
        """Generates presentation for the info-bar role."""

        return []

    def _generate_input_method_window(self, _obj, **_args):
        """Generates presentation for the input-method-window role."""

        return []

    def _generate_internal_frame(self, _obj, **_args):
        """Generates presentation for the internal-frame role."""

        return []

    def _generate_label(self, _obj, **_args):
        """Generates presentation for the label role."""

        return []

    def _generate_landmark(self, _obj, **_args):
        """Generates presentation for the landmark role."""

        return []

    def _generate_layered_pane(self, _obj, **_args):
        """Generates presentation for the layered-pane role."""

        return []

    def _generate_level_bar(self, _obj, **_args):
        """Generates presentation for the level-bar role."""

        return []

    def _generate_link(self, _obj, **_args):
        """Generates presentation for the link role."""

        return []

    def _generate_list(self, _obj, **_args):
        """Generates presentation for the list role."""

        return []

    def _generate_list_box(self, _obj, **_args):
        """Generates presentation for the list-box role."""

        return []

    def _generate_list_item(self, _obj, **_args):
        """Generates presentation for the list-item role."""

        return []

    def _generate_log(self, _obj, **_args):
        """Generates presentation for the log role."""

        return []

    def _generate_mark(self, _obj, **_args):
        """Generates presentation for the mark role."""

        return []

    def _generate_marquee(self, _obj, **_args):
        """Generates presentation for the marquee role."""

        return []

    def _generate_math(self, _obj, **_args):
        """Generates presentation for the math role."""

        return []

    def _generate_math_enclosed(self, _obj, **_args):
        """Generates presentation for the math-enclosed role."""

        return []

    def _generate_math_fenced(self, _obj, **_args):
        """Generates presentation for the math-fenced role."""

        return []

    def _generate_math_fraction(self, _obj, **_args):
        """Generates presentation for the math-fraction role."""

        return []

    def _generate_math_multiscript(self, _obj, **_args):
        """Generates presentation for the math-multiscript role."""

        return []

    def _generate_math_root(self, _obj, **_args):
        """Generates presentation for the math-root role."""

        return []

    def _generate_math_row(self, _obj, **_args):
        """Generates presentation for the math-row role."""

        return []

    def _generate_math_script_subsuper(self, _obj, **_args):
        """Generates presentation for the math script subsuper role."""

        return []

    def _generate_math_script_underover(self, _obj, **_args):
        """Generates presentation for the math script underover role."""

        return []

    def _generate_math_table(self, _obj, **_args):
        """Generates presentation for the math-table role."""

        return []

    def _generate_menu(self, _obj, **_args):
        """Generates presentation for the menu role."""

        return []

    def _generate_menu_bar(self, _obj, **_args):
        """Generates presentation for the menu-bar role."""

        return []

    def _generate_menu_item(self, _obj, **_args):
        """Generates presentation for the menu-item role."""

        return []

    def _generate_notification(self, _obj, **_args):
        """Generates presentation for the notification role."""

        return []

    def _generate_option_pane(self, _obj, **_args):
        """Generates presentation for the option-pane role."""

        return []

    def _generate_page(self, _obj, **_args):
        """Generates presentation for the page role."""

        return []

    def _generate_page_tab(self, _obj, **_args):
        """Generates presentation for the page-tab role."""

        return []

    def _generate_page_tab_list(self, _obj, **_args):
        """Generates presentation for the page-tab-list role."""

        return []

    def _generate_panel(self, _obj, **_args):
        """Generates presentation for the panel role."""

        return []

    def _generate_paragraph(self, _obj, **_args):
        """Generates presentation for the paragraph role."""

        return []

    def _generate_password_text(self, _obj, **_args):
        """Generates presentation for the password-text role."""

        return []

    def _generate_popup_menu(self, _obj, **_args):
        """Generates presentation for the popup-menu role."""

        return []

    def _generate_progress_bar(self, _obj, **_args):
        """Generates presentation for the progress-bar role."""

        return []

    def _generate_push_button(self, _obj, **_args):
        """Generates presentation for the push-button role."""

        return []

    def _generate_push_button_menu(self, _obj, **_args):
        """Generates presentation for the push-button-menu role."""

        return []

    def _generate_radio_button(self, _obj, **_args):
        """Generates presentation for the radio-button role."""

        return []

    def _generate_radio_menu_item(self, _obj, **_args):
        """Generates presentation for the radio-menu-item role."""

        return []

    def _generate_rating(self, _obj, **_args):
        """Generates presentation for the rating role."""

        return []

    def _generate_region(self, _obj, **_args):
        """Generates presentation for the region landmark role."""

        return []

    def _generate_root_pane(self, _obj, **_args):
        """Generates presentation for the root-pane role."""

        return []

    def _generate_row_header(self, _obj, **_args):
        """Generates presentation for the row-header role."""

        return []

    def _generate_ruler(self, _obj, **_args):
        """Generates presentation for the ruler role."""

        return []

    def _generate_scroll_bar(self, _obj, **_args):
        """Generates presentation for the scroll-bar role."""

        return []

    def _generate_scroll_pane(self, _obj, **_args):
        """Generates presentation for the scroll-pane role."""

        return []

    def _generate_section(self, _obj, **_args):
        """Generates presentation for the section role."""

        return []

    def _generate_separator(self, _obj, **_args):
        """Generates presentation for the separator role."""

        return []

    def _generate_slider(self, _obj, **_args):
        """Generates presentation for the slider role."""

        return []

    def _generate_spin_button(self, _obj, **_args):
        """Generates presentation for the spin-button role."""

        return []

    def _generate_split_pane(self, _obj, **_args):
        """Generates presentation for the split-pane role."""

        return []

    def _generate_static(self, _obj, **_args):
        """Generates presentation for the static role."""

        return []

    def _generate_status_bar(self, _obj, **_args):
        """Generates presentation for the status-bar role."""

        return []

    def _generate_subscript(self, _obj, **_args):
        """Generates presentation for the subscript role."""

        return []

    def _generate_suggestion(self, _obj, **_args):
        """Generates presentation for the suggestion role."""

        return []

    def _generate_superscript(self, _obj, **_args):
        """Generates presentation for the superscript role."""

        return []

    def _generate_switch(self, _obj, **_args):
        """Generates presentation for the switch role."""

        return []

    def _generate_table(self, _obj, **_args):
        """Generates presentation for the table role."""

        return []

    def _generate_table_cell(self, _obj, **_args):
        """Generates presentation for the table-cell role."""

        return []

    def _generate_table_cell_in_row(self, _obj, **_args):
        """Generates presentation for the table-cell role in the context of its row."""

        return []

    def _generate_table_column_header(self, _obj, **_args):
        """Generates presentation for the table-column-header role."""

        return []

    def _generate_table_row(self, _obj, **_args):
        """Generates presentation for the table-row role."""

        return []

    def _generate_table_row_header(self, _obj, **_args):
        """Generates presentation for the table-row-header role."""

        return []

    def _generate_tearoff_menu_item(self, _obj, **_args):
        """Generates presentation for the tearoff-menu-item role."""

        return []

    def _generate_terminal(self, _obj, **_args):
        """Generates presentation for the terminal role."""

        return []

    def _generate_text(self, _obj, **_args):
        """Generates presentation for the text role."""

        return []

    def _generate_timer(self, _obj, **_args):
        """Generates presentation for the timer role."""

        return []

    def _generate_title_bar(self, _obj, **_args):
        """Generates presentation for the title-bar role."""

        return []

    def _generate_toggle_button(self, _obj, **_args):
        """Generates presentation for the toggle-button role."""

        return []

    def _generate_tool_bar(self, _obj, **_args):
        """Generates presentation for the tool-bar role."""

        return []

    def _generate_tool_tip(self, _obj, **_args):
        """Generates presentation for the tool-tip role."""

        return []

    def _generate_tree(self, _obj, **_args):
        """Generates presentation for the tree role."""

        return []

    def _generate_tree_item(self, _obj, **_args):
        """Generates presentation for the tree-item role."""

        return []

    def _generate_tree_table(self, _obj, **_args):
        """Generates presentation for the tree-table role."""

        return []

    def _generate_unknown(self, _obj, **_args):
        """Generates presentation for the unknown role."""

        return []

    def _generate_video(self, _obj, **_args):
        """Generates presentation for the video role."""

        return []

    def _generate_viewport(self, _obj, **_args):
        """Generates presentation for the viewport role."""

        return []

    def _generate_window(self, _obj, **_args):
        """Generates presentation for the window role."""

        return []

Generator.start_cache_clearing_thread()
