# Orca
#
# Copyright 2005-2009 Sun Microsystems Inc.
# Copyright 2010-2011 Orca Team
# Copyright 2011-2015 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=wrong-import-position
# pylint: disable=too-many-locals
"""Produces braille presentation for accessible objects."""
__id__ = "$Id$"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2005-2009 Sun Microsystems Inc." \
"Copyright (c) 2010-2011 Orca Team" \
"Copyright (c) 2011-2015 Igalia, S.L."
__license__ = "LGPL"
import gi
gi.require_version("Atspi", "2.0")
from gi.repository import Atspi
from orca import braille
from orca import braille_generator
from orca import debug
from orca import focus_manager
from orca import messages
from orca import object_properties
from orca.ax_object import AXObject
from orca.ax_table import AXTable
from orca.ax_utilities import AXUtilities
class BrailleGenerator(braille_generator.BrailleGenerator):
"""Produces braille presentation for accessible objects."""
@staticmethod
def log_generator_output(func):
"""Decorator for logging."""
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
tokens = [f"WEB BRAILLE GENERATOR: {func.__name__}:", result]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return result
return wrapper
def get_localized_role_name(self, obj, **args):
if not self._script.utilities.inDocumentContent(obj):
return super().get_localized_role_name(obj, **args)
role_description = AXObject.get_role_description(obj, True)
if role_description:
return role_description
return super().get_localized_role_name(obj, **args)
@log_generator_output
def _generate_accessible_role(self, obj, **args):
"""Prevents some roles from being displayed."""
if not self._script.utilities.inDocumentContent(obj):
return super()._generate_accessible_role(obj, **args)
role_description = AXObject.get_role_description(obj, True)
if role_description:
return [role_description]
# TODO - JD: Can this logic be moved to the default braille generator?
do_not_display = [Atspi.Role.FORM,
Atspi.Role.PARAGRAPH,
Atspi.Role.STATIC,
Atspi.Role.SECTION,
Atspi.Role.REDUNDANT_OBJECT,
Atspi.Role.UNKNOWN]
if not AXUtilities.is_focusable(obj):
do_not_display.extend([Atspi.Role.LIST,
Atspi.Role.LIST_ITEM,
Atspi.Role.COLUMN_HEADER,
Atspi.Role.ROW_HEADER,
Atspi.Role.TABLE_CELL,
Atspi.Role.PANEL])
if args.get("startOffset") is not None and args.get("endOffset") is not None:
do_not_display.append(Atspi.Role.ALERT)
result = []
role = args.get('role', AXObject.get_role(obj))
if AXUtilities.is_heading(obj, args.get("role")):
level = self._script.utilities.headingLevel(obj)
result.append(object_properties.ROLE_HEADING_LEVEL_BRAILLE % level)
return result
if self._script.utilities.isLink(obj) \
and obj == focus_manager.get_manager().get_locus_of_focus():
if AXUtilities.is_image(AXObject.get_parent(obj)):
result.append(messages.IMAGE_MAP_LINK)
elif role not in do_not_display:
label = AXTable.get_label_for_cell_coordinates(obj)
if label:
result.append(label)
else:
result = super()._generate_accessible_role(obj, **args)
if args.get("index", 0) == args.get("total", 1) - 1 \
and (AXUtilities.is_image(obj, args.get("role")) \
or self._script.utilities.treatAsTextObject(obj)):
heading = AXObject.find_ancestor(obj, AXUtilities.is_heading)
if heading is not None:
result.extend(self._generate_accessible_role(heading))
return result
@log_generator_output
def _generate_accessible_label(self, obj, **args):
if not self._script.utilities.inDocumentContent(obj):
return super()._generate_accessible_label(obj, **args)
label, _objects = self._script.utilities.inferLabelFor(obj)
if label:
return [label]
return super()._generate_accessible_label(obj, **args)
@log_generator_output
def _generate_accessible_label_and_name(self, obj, **args):
if not self._script.utilities.inDocumentContent(obj):
return super()._generate_accessible_label_and_name(obj, **args)
if self._script.utilities.isTextBlockElement(obj):
return []
if AXUtilities.is_editable(obj) \
and self._script.utilities.isCodeDescendant(obj):
return []
role = args.get('role', AXObject.get_role(obj))
if role == Atspi.Role.LABEL and AXObject.supports_text(obj):
return []
return super()._generate_accessible_label_and_name(obj, **args)
@log_generator_output
def _generate_accessible_description(self, obj, **args):
if not self._script.utilities.inDocumentContent(obj):
return super()._generate_accessible_description(obj, **args)
if self._script.utilities.preferDescriptionOverName(obj):
return []
return super()._generate_accessible_description(obj, **args)
@log_generator_output
def _generate_accessible_name(self, obj, **args):
if not self._script.utilities.inDocumentContent(obj):
return super()._generate_accessible_name(obj, **args)
braille_label = AXObject.get_attributes_dict(obj).get("braillelabel")
if braille_label:
return [braille_label]
if self._script.utilities.preferDescriptionOverName(obj):
return [AXObject.get_description(obj)]
if AXObject.get_name(obj) and not self._script.utilities.hasValidName(obj):
return []
result = super()._generate_accessible_name(obj, **args)
if result and result[0] and not AXUtilities.has_explicit_name(obj):
result[0] = result[0].strip()
elif not result and AXUtilities.is_check_box(obj):
grid_cell = AXObject.find_ancestor(obj, AXUtilities.is_grid_cell)
if grid_cell:
return super()._generate_accessible_name(grid_cell, **args)
return result
@log_generator_output
def _generate_real_active_descendant_displayed_text(self, obj, **args):
if not self._script.utilities.inDocumentContent(obj):
return super()._generate_real_active_descendant_displayed_text(obj, **args)
rad = self._script.utilities.realActiveDescendant(obj)
return self._generate_text_content(rad, **args)
def generate_braille(self, obj, **args):
if not self._script.utilities.inDocumentContent(obj):
tokens = ["WEB:", obj, "is not in document content. Calling default braille generator."]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return super().generate_braille(obj, **args)
tokens = ["WEB: Generating braille for document object", obj, args]
debug.print_tokens(debug.LEVEL_INFO, tokens, True, True)
result = []
args['includeContext'] = not self._script.utilities.inDocumentContent(obj)
if self._script.utilities.isClickableElement(obj) or self._script.utilities.isLink(obj):
args["role"] = Atspi.Role.LINK
elif self._script.utilities.isCustomImage(obj):
args["role"] = Atspi.Role.IMAGE
elif self._script.utilities.isAnchor(obj):
args["role"] = Atspi.Role.STATIC
elif self._script.utilities.treatAsDiv(obj, offset=args.get('startOffset')):
args["role"] = Atspi.Role.SECTION
if AXUtilities.is_menu_item(obj):
combo_box = AXObject.find_ancestor(obj, AXUtilities.is_combo_box)
if combo_box and not AXUtilities.is_expanded(combo_box):
obj = combo_box
result.extend(super().generate_braille(obj, **args))
del args['includeContext']
return result
def generate_contents(self, contents, **args):
if not contents:
return []
result = []
contents = self._script.utilities.filterContentsForPresentation(contents, True)
document = args.get("documentFrame")
obj, offset = self._script.utilities.getCaretContext(documentFrame=document)
index = self._script.utilities.findObjectInContents(obj, offset, contents)
last_region = None
focused_region = None
for i, content in enumerate(contents):
acc, start, end, string = content
regions, f_region = self.generate_braille(
acc, startOffset=start, endOffset=end, caretOffset=offset, string=string,
index=i, total=len(contents))
if not regions:
continue
if i == index:
focused_region = f_region
if last_region and regions:
last_char = next_char = ""
if last_region.string:
last_char = last_region.string[-1]
if regions[0].string:
next_char = regions[0].string[0]
if self._script.utilities.needsSeparator(last_char, next_char):
regions.insert(0, braille.Region(" "))
last_region = regions[-1]
result.append(regions)
return result, focused_region