%PDF- %PDF-
Direktori : /usr/lib/python3/dist-packages/orca/ |
Current File : //usr/lib/python3/dist-packages/orca/structural_navigation.py |
# Orca # # Copyright 2005-2009 Sun Microsystems Inc. # Copyright 2010-2013 The Orca Team # # 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. """Implements structural navigation.""" __id__ = "$Id$" __version__ = "$Revision$" __date__ = "$Date$" __copyright__ = "Copyright (c) 2005-2009 Sun Microsystems Inc." \ "Copyright (c) 2010-2013 The Orca Team" __license__ = "LGPL" import gi gi.require_version("Atspi", "2.0") from gi.repository import Atspi from . import cmdnames from . import debug from . import focus_manager from . import guilabels from . import input_event from . import keybindings from . import messages from . import object_properties from . import orca_gui_navlist from . import orca_state from . import settings from . import settings_manager from .ax_collection import AXCollection from .ax_event_synthesizer import AXEventSynthesizer from .ax_hypertext import AXHypertext from .ax_object import AXObject from .ax_selection import AXSelection from .ax_table import AXTable from .ax_text import AXText from .ax_utilities import AXUtilities ########################################################################### # # # StructuralNavigationObject # # # ########################################################################### class StructuralNavigationObject: """Represents a document object which has identifiable characteristics which can be used for the purpose of navigation to and among instances of that object. These characteristics may be something as simple as a role and/or a state of interest. Or they may be something more complex such as character counts, text attributes, and other object attributes. """ def __init__(self, structuralNavigation, objType, bindings, predicate, criteria, presentation, dialogData, getter): """Creates a new structural navigation object. Arguments: - structuralNavigation: the StructuralNavigation class associated with this object. - objType: the type (e.g. BLOCKQUOTE) associated with this object. - bindings: a dictionary of all of the possible bindings for this object. In the case of all but the "atLevel" bindings, each binding takes the form of [keysymstring, modifiers, description]. The goPreviousAtLevel and goNextAtLevel bindings are each a list of bindings in that form. - predicate: the method to use to verify if a given accessible matches this structural navigation object. Used only when the collection interface does not provide a way for us to specify needed condition(s). - criteria: a method which returns a MatchRule object which is used to find all matching objects via AtspiCollection. - presentation: the method which should be called after performing the search for the structural navigation object. - dialogData: the method which returns the title, column headers, and row data which should be included in the "list of" dialog for the structural navigation object. - getter: The function which should be used instead of the criteria and predicate. """ self.structuralNavigation = structuralNavigation self.objType = objType self.bindings = bindings self.predicate = predicate self.criteria = criteria self.present = presentation self._dialogData = dialogData self.getter = getter self.inputEventHandlers = {} self.keyBindings = keybindings.KeyBindings() self.functions = [] self._setUpHandlersAndBindings() def _setUpHandlersAndBindings(self): """Adds the inputEventHandlers and keyBindings for this object.""" # Set up the basic handlers. These are our traditional goPrevious # and goNext functions. # previousBinding = self.bindings.get("previous") if previousBinding: [keysymstring, modifiers, description] = previousBinding handlerName = f"{self.objType}GoPrevious" self.inputEventHandlers[handlerName] = \ input_event.InputEventHandler(self.goPrevious, description) self.keyBindings.add( keybindings.KeyBinding( keysymstring, keybindings.defaultModifierMask, modifiers, self.inputEventHandlers[handlerName])) self.functions.append(self.goPrevious) nextBinding = self.bindings.get("next") if nextBinding: [keysymstring, modifiers, description] = nextBinding handlerName = f"{self.objType}GoNext" self.inputEventHandlers[handlerName] = \ input_event.InputEventHandler(self.goNext, description) self.keyBindings.add( keybindings.KeyBinding( keysymstring, keybindings.defaultModifierMask, modifiers, self.inputEventHandlers[handlerName])) self.functions.append(self.goNext) listBinding = self.bindings.get("list") if listBinding: [keysymstring, modifiers, description] = listBinding handlerName = f"{self.objType}ShowList" self.inputEventHandlers[handlerName] = \ input_event.InputEventHandler(self.showList, description) self.keyBindings.add( keybindings.KeyBinding( keysymstring, keybindings.defaultModifierMask, modifiers, self.inputEventHandlers[handlerName])) self.functions.append(self.showList) # Set up the "at level" handlers (e.g. to navigate among headings # at the specified level). # previousAtLevel = self.bindings.get("previousAtLevel") or [] for i, binding in enumerate(previousAtLevel): level = i + 1 handler = self.goPreviousAtLevelFactory(level) handlerName = "%sGoPreviousLevel%dHandler" % (self.objType, level) keysymstring, modifiers, description = binding self.inputEventHandlers[handlerName] = \ input_event.InputEventHandler(handler, description) self.keyBindings.add( keybindings.KeyBinding( keysymstring, keybindings.defaultModifierMask, modifiers, self.inputEventHandlers[handlerName])) self.functions.append(handler) nextAtLevel = self.bindings.get("nextAtLevel") or [] for i, binding in enumerate(nextAtLevel): level = i + 1 handler = self.goNextAtLevelFactory(level) handlerName = "%sGoNextLevel%dHandler" % (self.objType, level) keysymstring, modifiers, description = binding self.inputEventHandlers[handlerName] = \ input_event.InputEventHandler(handler, description) self.keyBindings.add( keybindings.KeyBinding( keysymstring, keybindings.defaultModifierMask, modifiers, self.inputEventHandlers[handlerName])) self.functions.append(handler) listAtLevel = self.bindings.get("listAtLevel") or [] for i, binding in enumerate(listAtLevel): level = i + 1 handler = self.showListAtLevelFactory(level) handlerName = "%sShowListAtLevel%dHandler" % (self.objType, level) keysymstring, modifiers, description = binding self.inputEventHandlers[handlerName] = \ input_event.InputEventHandler(handler, description) self.keyBindings.add( keybindings.KeyBinding( keysymstring, keybindings.defaultModifierMask, modifiers, self.inputEventHandlers[handlerName])) self.functions.append(handler) # Set up the "directional" handlers (e.g. for table cells. Live # region support has a handler to go to the last live region, # so we'll handle that here as well). # directions = {} directions["Left"] = self.bindings.get("left") directions["Right"] = self.bindings.get("right") directions["Up"] = self.bindings.get("up") directions["Down"] = self.bindings.get("down") directions["First"] = self.bindings.get("first") directions["Last"] = self.bindings.get("last") directions["Start"] = self.bindings.get("start") directions["End"] = self.bindings.get("end") for direction in directions: binding = directions.get(direction) if not binding: continue handler = self.goDirectionFactory(direction) handlerName = f"{self.objType}Go{direction}" keysymstring, modifiers, description = binding self.inputEventHandlers[handlerName] = \ input_event.InputEventHandler(handler, description) self.keyBindings.add( keybindings.KeyBinding( keysymstring, keybindings.defaultModifierMask, modifiers, self.inputEventHandlers[handlerName])) self.functions.append(handler) def goPrevious(self, script, inputEvent): """Go to the previous object.""" self.structuralNavigation.goObject(self, False, inputEvent) def goNext(self, script, inputEvent): """Go to the next object.""" self.structuralNavigation.goObject(self, True, inputEvent) def showList(self, script, inputEvent): """Show a list of all the items with this object type.""" objects = self.structuralNavigation._getAll(self) def _isValidMatch(x): if AXObject.is_dead(x): return False return not (script.utilities.isHidden(x) or script.utilities.isEmpty(x)) objects = list(filter(_isValidMatch, objects)) if self.predicate is not None: objects = list(filter(self.predicate, objects)) if self._dialogData is None: msg = "STRUCTURAL NAVIGATION: Cannot show list without dialog data" debug.printMessage(debug.LEVEL_INFO, msg, True) return title, columnHeaders, rowData = self._dialogData() count = len(objects) title = f"{title}: {messages.itemsFound(count)}" if not count: script.presentMessage(title) return currentObject, offset = script.utilities.getCaretContext() try: index = objects.index(currentObject) except Exception: index = 0 rows = [[obj, -1] + rowData(obj) for obj in objects] orca_gui_navlist.showUI(title, columnHeaders, rows, index) def goPreviousAtLevelFactory(self, level): """Generates a goPrevious method for the specified level. Right now, this is just for headings, but it may have applicability for other objects such as list items (i.e. for level-based navigation in an outline or other multi-tiered list. Arguments: - level: the desired level of the object as an int. """ def goPreviousAtLevel(script, inputEvent): self.structuralNavigation.goObject(self, False, inputEvent, arg=level) return goPreviousAtLevel def goNextAtLevelFactory(self, level): """Generates a goNext method for the specified level. Right now, this is just for headings, but it may have applicability for other objects such as list items (i.e. for level-based navigation in an outline or other multi-tiered list. Arguments: - level: the desired level of the object as an int. """ def goNextAtLevel(script, inputEvent): self.structuralNavigation.goObject(self, True, inputEvent, arg=level) return goNextAtLevel def showListAtLevelFactory(self, level): """Generates a showList method for the specified level. Right now, this is just for headings, but it may have applicability for other objects such as list items (i.e. for level-based navigation in an outline or other multi-tiered list. Arguments: - level: the desired level of the object as an int. """ def showListAtLevel(script, inputEvent): objects = self.structuralNavigation._getAll(self, arg=level) def _isValidMatch(x): return not (script.utilities.isHidden(x) or script.utilities.isEmpty(x)) objects = list(filter(_isValidMatch, objects)) if self.predicate is not None: objects = list(filter(self.predicate, objects)) title, columnHeaders, rowData = self._dialogData(arg=level) count = len(objects) title = f"{title}: {messages.itemsFound(count)}" if not count: script.presentMessage(title) return currentObject, offset = script.utilities.getCaretContext() try: index = objects.index(currentObject) except Exception: index = 0 rows = [[obj, -1] + rowData(obj) for obj in objects] orca_gui_navlist.showUI(title, columnHeaders, rows, index) return showListAtLevel def goDirectionFactory(self, direction): """Generates the methods for navigation in a particular direction.""" def goLastLiveRegion(script, inputEvent): """Go to the last liveRegion.""" if settings.inferLiveRegions: script.liveRegionManager.goLastLiveRegion() else: script.presentMessage(messages.LIVE_REGIONS_OFF) def goContainerEdge(script, inputEvent): isStart = direction == "Start" self.structuralNavigation.goEdge(self, isStart, inputEvent) if self.objType == StructuralNavigation.CONTAINER: return goContainerEdge elif self.objType == StructuralNavigation.LIVE_REGION \ and direction == "Last": return goLastLiveRegion ############################################################################# # # # StructuralNavigation # # # ############################################################################# class StructuralNavigation: """This class implements the structural navigation functionality which is available to scripts. Scripts interested in implementing structural navigation need to override getEnabledStructuralNavigationTypes() and return a list of StructuralNavigation object types which should be enabled. """ # The available object types. # # Convenience methods have been put into place whereby one can # create an object (FOO = "foo"), and then provide the following # methods: _fooBindings(), _fooPredicate(), _fooCriteria(), and # _fooPresentation(). With these in place, and with the object # FOO included among the object types returned by the script's # getEnabledStructuralNavigationTypes(), the StructuralNavigation # object should be created and set up automagically. At least that # is the idea. :-) This hopefully will also enable easy re-definition # of existing StructuralNavigationObjects on a script-by-script basis. # For instance, in the soffice script, overriding _blockquotePredicate # should be all that is needed to implement navigation by blockquote # in OOo Writer documents. # BLOCKQUOTE = "blockquote" BUTTON = "button" CHECK_BOX = "checkBox" CHUNK = "chunk" CLICKABLE = "clickable" COMBO_BOX = "comboBox" CONTAINER = "container" ENTRY = "entry" FORM_FIELD = "formField" HEADING = "heading" IMAGE = "image" IFRAME = "iframe" LANDMARK = "landmark" LINK = "link" LIST = "list" # Bulleted/numbered lists LIST_ITEM = "listItem" # Bulleted/numbered list items LIVE_REGION = "liveRegion" PARAGRAPH = "paragraph" RADIO_BUTTON = "radioButton" SEPARATOR = "separator" TABLE = "table" UNVISITED_LINK = "unvisitedLink" VISITED_LINK = "visitedLink" # Roles which are recognized as being potential "large objects" # or "chunks." Note that this refers to AT-SPI roles. # OBJECT_ROLES = [Atspi.Role.HEADING, Atspi.Role.LIST_ITEM, Atspi.Role.MATH, Atspi.Role.PARAGRAPH, Atspi.Role.STATIC, Atspi.Role.COLUMN_HEADER, Atspi.Role.ROW_HEADER, Atspi.Role.TABLE_CELL, Atspi.Role.TABLE_ROW, Atspi.Role.TEXT, Atspi.Role.SECTION, Atspi.Role.ARTICLE, Atspi.Role.DESCRIPTION_TERM, Atspi.Role.DESCRIPTION_VALUE, Atspi.Role.DOCUMENT_EMAIL, Atspi.Role.DOCUMENT_FRAME, Atspi.Role.DOCUMENT_PRESENTATION, Atspi.Role.DOCUMENT_SPREADSHEET, Atspi.Role.DOCUMENT_TEXT, Atspi.Role.DOCUMENT_WEB] CONTAINER_ROLES = [Atspi.Role.BLOCK_QUOTE, Atspi.Role.DESCRIPTION_LIST, Atspi.Role.FORM, Atspi.Role.FOOTER, Atspi.Role.HEADER, Atspi.Role.LANDMARK, Atspi.Role.LOG, Atspi.Role.LIST, Atspi.Role.MARQUEE, Atspi.Role.PANEL, Atspi.Role.SECTION, Atspi.Role.TABLE, Atspi.Role.TREE, Atspi.Role.TREE_TABLE] def __init__(self, script, enabledTypes, enabled=False): """Creates an instance of the StructuralNavigation class. Arguments: - script: the script which which this instance is associated. - enabledTypes: a list of StructuralNavigation object types which the script is interested in supporting. - enabled: Whether structural navigation should start out enabled. For instance, in Gecko by default we do what it enabled; in soffice, we would want to start out with it disabled and have the user enable it via a keystroke when desired. """ self._script = script self.enabled = enabled # To make it possible for focus mode to suspend this navigation without # changing the user's preferred setting. self._suspended = False # Create all of the StructuralNavigationObject's in which the # script is interested, using the convenience method # self.enabledObjects = {} for objType in enabledTypes: self.enabledObjects[objType] = \ self.structuralNavigationObjectCreator(objType) self.functions = [] self._last_input_event = None self._handlers = self.get_handlers(True) self._bindings = keybindings.KeyBindings() # When navigating in a non-uniform table, one can move to a # cell which spans multiple rows and/or columns. When moving # beyond that cell, into a cell that does NOT span multiple # rows/columns, we want to be sure we land in the right place. # Therefore, we'll store the coordinates from "our perspective." # self.lastTableCell = [-1, -1] self._objectCache = {} self._inModalDialog = False def clearCache(self, document=None): if document: self._objectCache[hash(document)] = {} else: self._objectCache = {} def structuralNavigationObjectCreator(self, name): """This convenience method creates a StructuralNavigationObject with the specified name and associated characteristics. (See the "Objects" section of code near the end of this class. Creators of StructuralNavigationObject's can still do things the old fashioned way should they so choose, by creating the instance and then adding it via addObject(). Arguments: - name: the name/objType associated with this object. """ # Bindings and presentation are mandatory. bindings = eval(f"self._{name}Bindings()") presentation = eval(f"self._{name}Presentation") # Predicates should be the exception; not the rule. try: predicate = eval(f"self._{name}Predicate") except Exception: predicate = None # Dialogs are nice, but we shouldn't insist upon them. try: dialogData = eval(f"self._{name}DialogData") except Exception: dialogData = None # Criteria is the present, but being phased out. try: criteria = eval(f"self._{name}Criteria") except Exception: criteria = None # Getters are the future! try: getter = eval(f"self._{name}Getter") except Exception: getter = None return StructuralNavigationObject(self, name, bindings, predicate, criteria, presentation, dialogData, getter) def addObject(self, objType, structuralNavigationObject): """Adds structuralNavigationObject to the dictionary of enabled objects. Arguments: - objType: the name/object type of the StructuralNavigationObject. - structuralNavigationObject: the StructuralNavigationObject to add. """ self.enabledObjects[objType] = structuralNavigationObject def get_handlers(self, refresh=False): """Returns the structural navigation input event handlers.""" if refresh: msg = "STRUCTURAL NAVIGATION: Refreshing handlers." debug.printMessage(debug.LEVEL_INFO, msg, True) self._setup_handlers() return self._handlers def _setup_handlers(self): """Sets up the structural navigation input event handlers.""" self._handlers = {} self.functions = [] if not len(self.enabledObjects): return self._handlers["toggleStructuralNavigationHandler"] = \ input_event.InputEventHandler( self.toggleStructuralNavigation, cmdnames.STRUCTURAL_NAVIGATION_TOGGLE, enabled = not self._suspended) for structuralNavigationObject in self.enabledObjects.values(): handlers = structuralNavigationObject.inputEventHandlers for key in handlers: handlers[key].set_enabled(not self._suspended and self.enabled) self._handlers.update(handlers) self.functions.extend(structuralNavigationObject.functions) msg = f"STRUCTURAL NAVIGATION: Handlers set up. Suspended: {self._suspended}" debug.printMessage(debug.LEVEL_INFO, msg, True) def get_bindings(self, refresh=False, is_desktop=True): """Returns the structural navigation keybindings.""" if refresh: msg = "STRUCTURAL NAVIGATION: Refreshing bindings." debug.printMessage(debug.LEVEL_INFO, msg, True) self._setup_bindings() elif self._bindings.isEmpty(): self._setup_bindings() return self._bindings def _setup_bindings(self): """Sets up the structural navigation keybindings.""" self._bindings = keybindings.KeyBindings() if not len(self.enabledObjects): return self._bindings.add( keybindings.KeyBinding( "z", keybindings.defaultModifierMask, keybindings.ORCA_MODIFIER_MASK, self._handlers["toggleStructuralNavigationHandler"], 1, not self._suspended)) for structuralNavigationObject in self.enabledObjects.values(): bindings = structuralNavigationObject.keyBindings.keyBindings for keybinding in bindings: keybinding.set_enabled(self.enabled and not self._suspended) self._bindings.add(keybinding) # This pulls in the user's overrides to alternative keys. self._bindings = settings_manager.getManager().overrideKeyBindings( self._handlers, self._bindings, False) msg = f"STRUCTURAL NAVIGATION: Bindings set up. Suspended: {self._suspended}" debug.printMessage(debug.LEVEL_INFO, msg, True) tokens = [self._bindings] debug.printTokens(debug.LEVEL_INFO, tokens, True) def last_input_event_was_navigation_command(self): """Returns true if the last input event was a navigation command.""" result = self._last_input_event is not None \ and (self._last_input_event == orca_state.lastNonModifierKeyEvent \ or orca_state.lastNonModifierKeyEvent.isReleaseFor(self._last_input_event)) if self._last_input_event is not None: string = self._last_input_event.asSingleLineString() else: string = "None" msg = f"STRUCTURAL NAVIGATION: Last navigation event ({string}) is last key event: {result}" debug.printMessage(debug.LEVEL_INFO, msg, True) return result def refresh_bindings_and_grabs(self, script, reason=""): """Refreshes structural navigation bindings and grabs for script.""" msg = "STRUCTURAL NAVIGATION: Refreshing bindings and grabs" if reason: msg += f": {reason}" debug.printMessage(debug.LEVEL_INFO, msg, True) for binding in self._bindings.keyBindings: script.keyBindings.remove(binding, includeGrabs=True) self._handlers = self.get_handlers(True) self._bindings = self.get_bindings(True) for binding in self._bindings.keyBindings: script.keyBindings.add(binding, includeGrabs=not self._suspended) def toggleStructuralNavigation(self, script, inputEvent, presentMessage=True): """Toggles structural navigation keys.""" self.enabled = not self.enabled if self.enabled: string = messages.STRUCTURAL_NAVIGATION_KEYS_ON else: string = messages.STRUCTURAL_NAVIGATION_KEYS_OFF self._handlers = self.get_handlers(True) self._bindings = self.get_bindings(True) self.refresh_bindings_and_grabs(script, "toggling structural navigation") if presentMessage: self._script.presentMessage(string) return True def suspend_commands(self, script, suspended, reason=""): """Suspends structural navigation independent of the enabled setting.""" if suspended == self._suspended: return msg = f"STRUCTURAL NAVIGATION: Suspended: {suspended}" if reason: msg += f": {reason}" debug.printMessage(debug.LEVEL_INFO, msg, True) debug.printMessage(debug.LEVEL_INFO, msg, True) self._suspended = suspended self.refresh_bindings_and_grabs(script, f"Suspended changed to {suspended}") ######################################################################### # # # Methods for Moving to Objects # # # ######################################################################### def _getAll(self, structuralNavigationObject, arg=None): """Returns all the instances of structuralNavigationObject.""" modalDialog = self._script.utilities.getModalDialog( focus_manager.getManager().get_locus_of_focus()) inModalDialog = bool(modalDialog) if self._inModalDialog != inModalDialog: msg = ( f"STRUCTURAL NAVIGATION: in modal dialog has changed from " f"{self._inModalDialog} to {inModalDialog}" ) debug.printMessage(debug.LEVEL_INFO, msg, True) self.clearCache() self._inModalDialog = inModalDialog document = self._script.utilities.documentFrame() cache = self._objectCache.get(hash(document), {}) key = f"{structuralNavigationObject.objType}:{arg}" matches = cache.get(key, []) if matches: tokens = ["STRUCTURAL NAVIGATION: Returning", len(matches), "matches from cache"] debug.printTokens(debug.LEVEL_INFO, tokens, True) return matches.copy() if structuralNavigationObject.getter: matches = structuralNavigationObject.getter(document, arg) elif not structuralNavigationObject.criteria: return [] elif not AXObject.supports_collection(document): tokens = ["STRUCTURAL NAVIGATION:", document, "does not support collection"] debug.printTokens(debug.LEVEL_INFO, tokens, True) return [] else: rule = structuralNavigationObject.criteria(arg) matches = AXCollection.get_all_matches(document, rule) if inModalDialog: originalSize = len(matches) matches = [m for m in matches if AXObject.find_ancestor(m, lambda x: x == modalDialog)] tokens = ["STRUCTURAL NAVIGATION: Removed", {originalSize - len(matches)}, "objects outside of modal dialog", modalDialog] debug.printTokens(debug.LEVEL_INFO, tokens, True) rv = matches.copy() cache[key] = matches self._objectCache[hash(document)] = cache return rv def goEdge(self, structuralNavigationObject, isStart, event, container=None, arg=None): self._last_input_event = event if container is None: obj, offset = self._script.utilities.getCaretContext() container = self.getContainerForObject(obj) if container is None or AXObject.is_dead(container): structuralNavigationObject.present(None, arg) return if isStart: obj, offset = self._script.utilities.nextContext(container, -1) structuralNavigationObject.present(obj, offset) return # Unlike going to the start of the container, when we move to the next edge # we pass beyond it on purpose. This makes us consistent with NVDA. obj, offset = self._script.utilities.lastContext(container) newObj, newOffset = self._script.utilities.nextContext(obj, offset) if not newObj: document = self._script.utilities.getDocumentForObject(obj) newObj = self._script.utilities.getNextObjectInDocument(obj, document) newContainer = self.getContainerForObject(newObj) if newObj and newContainer != container: structuralNavigationObject.present(newObj, newOffset) return if obj == container: obj = AXObject.get_child(obj, -1) structuralNavigationObject.present(obj, sameContainer=True) def goObject(self, structuralNavigationObject, isNext, event, obj=None, arg=None): """The method used for navigation among StructuralNavigationObjects which are not table cells. Arguments: - structuralNavigationObject: the StructuralNavigationObject which represents the object of interest. - isNext: If True, we're interested in the next accessible object which matches structuralNavigationObject. If False, we're interested in the previous accessible object which matches. - event: The input event triggering this navigation - obj: the current object (typically the locusOfFocus). - arg: optional arguments which may need to be passed along to the predicate, presentation method, etc. For instance, in the case of navigating amongst headings at a given level, the level is needed and passed in as arg. """ self._last_input_event = event matches = self._getAll(structuralNavigationObject, arg) if not matches: structuralNavigationObject.present(None, arg) return if not isNext: matches.reverse() def _isValidMatch(obj): if AXObject.is_dead(obj): return False if self._script.utilities.isHidden(obj) or self._script.utilities.isEmpty(obj): return False if structuralNavigationObject.predicate is None: return True return structuralNavigationObject.predicate(obj) def _getMatchingObjAndIndex(obj): while obj: if obj in matches: return obj, matches.index(obj) obj = AXObject.get_parent(obj) return None, -1 offset = 0 if not obj: obj, offset = self._script.utilities.getCaretContext() thisObj, index = _getMatchingObjAndIndex(obj) if thisObj: matches = matches[index:] obj = thisObj currentPath = AXObject.get_path(obj) for i, match in enumerate(matches): if not _isValidMatch(match): continue if AXObject.get_parent(match) == obj: comparison = AXHypertext.get_character_offset_in_parent(match) - offset else: path = AXObject.get_path(match) comparison = self._script.utilities.pathComparison(path, currentPath) if (comparison > 0 and isNext) or (comparison < 0 and not isNext): structuralNavigationObject.present(match, arg) return if not settings.wrappedStructuralNavigation: structuralNavigationObject.present(None, arg) return if not isNext: self._script.presentMessage(messages.WRAPPING_TO_BOTTOM) else: self._script.presentMessage(messages.WRAPPING_TO_TOP) matches = self._getAll(structuralNavigationObject, arg) if not isNext: matches.reverse() for match in matches: if _isValidMatch(match): structuralNavigationObject.present(match, arg) return structuralNavigationObject.present(None, arg) ######################################################################### # # # Methods for Presenting Objects # # # ######################################################################### def _getListDescription(self, obj): if AXUtilities.is_list(obj): children = [x for x in AXObject.iter_children(obj, AXUtilities.is_list_item)] if children: if self._script.utilities.nestingLevel(obj): return messages.nestedListItemCount(len(children)) else: return messages.listItemCount(len(children)) elif AXUtilities.is_description_list(obj): children = AXUtilities.find_all_description_terms(obj) if children: return messages.descriptionListTermCount(len(children)) return "" def _isContainer(self, obj): role = AXObject.get_role(obj) if role not in self.CONTAINER_ROLES: return False if role == Atspi.Role.SECTION \ and not self._script.utilities.isLandmark(obj) \ and not self._script.utilities.isBlockquote(obj): return False return self._script.utilities.inDocumentContent(obj) def getContainerForObject(self, obj): if not obj: return None if self._isContainer(obj): return obj return AXObject.find_ancestor(obj, self._isContainer) def getTableForCell(self, obj): """Looks for a table in the ancestry of obj, if obj is not a table. Arguments: - obj: the accessible object of interest. """ if obj and not AXUtilities.is_table(obj): obj = AXObject.find_ancestor(obj, AXUtilities.is_table) return obj def _getCaretPosition(self, obj): """Returns the [obj, characterOffset] where the caret should be positioned. For most scripts, the object should not change and the offset should be 0. That's not always the case with Gecko. Arguments: - obj: the accessible object in which the caret should be positioned. """ return self._script.utilities.getFirstCaretPosition(obj) def _setCaretPosition(self, obj, characterOffset): """Sets the caret at the specified offset within obj.""" objPath = AXObject.get_path(obj) objRole = AXObject.get_role(obj) if objRole == Atspi.Role.INVALID: return obj, characterOffset self._script.utilities.setCaretPosition(obj, characterOffset) AXObject.clear_cache( obj, False, "Structural navigation workaround for object destruction when setting caret.") if not AXUtilities.is_defunct(obj): return obj, characterOffset tokens = ["STRUCTURAL NAVIGATION:", obj, "became defunct after setting caret position"] debug.printTokens(debug.LEVEL_INFO, tokens, True) replicant = self._script.utilities.getObjectFromPath(objPath) if replicant and AXObject.get_role(replicant) == objRole: tokens = ["STRUCTURAL NAVIGATION: Updating obj to replicant", replicant] debug.printTokens(debug.LEVEL_INFO, tokens, True) obj = replicant return obj, characterOffset def _presentLine(self, obj, offset): """Presents the first line of the object to the user. Arguments: - obj: the accessible object to be presented. - offset: the character offset within obj. """ if not obj: return if self._presentWithSayAll(obj, offset): return self._script.updateBraille(obj) self._script.sayLine(obj) def _presentObject(self, obj, offset, priorObj=None): """Presents the entire object to the user. Arguments: - obj: the accessible object to be presented. - offset: the character offset within obj. """ if not obj: return if self._presentWithSayAll(obj, offset): return AXEventSynthesizer.scroll_to_top_edge(obj) self._script.presentObject(obj, offset=offset, priorObj=priorObj, interrupt=True) def _presentWithSayAll(self, obj, offset): if self._script.inSayAll() \ and settings_manager.getManager().getSetting('structNavInSayAll'): self._script.sayAll(obj, offset) return True return False def _getRoleName(self, obj): # Another case where we'll do this for now, and clean it up when # object presentation is refactored. return self._script.speechGenerator.getLocalizedRoleName(obj) def _getSelectedItem(self, obj): # Another case where we'll do this for now, and clean it up when # object presentation is refactored. if AXUtilities.is_combo_box(obj): obj = AXObject.get_child(obj, 0) if not AXObject.supports_selection(obj): return None return AXSelection.get_selected_child(obj, 0) def _getText(self, obj): # Another case where we'll do this for now, and clean it up when # object presentation is refactored. text = self._script.utilities.displayedText(obj) if not text: text = self._script.utilities.expandEOCs(obj) if not text: item = self._getSelectedItem(obj) if item: text = AXObject.get_name(item) if not text and AXUtilities.is_image(obj): text = AXObject.get_image_description(obj) or AXObject.get_description(obj) if not text: parent = AXObject.get_parent(obj) if AXUtilities.is_link(parent): text = AXHypertext.get_link_basename(parent) if not text and AXUtilities.is_list(obj): children = [x for x in AXObject.iter_children(obj, AXUtilities.is_list_item)] text = " ".join(list(map(self._getText, children))) return text def _getLabel(self, obj): # Another case where we'll do this for now, and clean it up when # object presentation is refactored. label = self._script.utilities.displayedLabel(obj) if not label: label, objects = self._script.labelInference.infer( obj, focusedOnly=False) return label def _getState(self, obj): # Another case where we'll do this for now, and clean it up when # object presentation is refactored. # For now, we'll just grab the spoken indicator from settings. # When object presentation is refactored, we can clean this up. if AXUtilities.is_check_box(obj): unchecked, checked, partially = object_properties.CHECK_BOX_INDICATORS_SPEECH if AXUtilities.is_indeterminate(obj): return partially if AXUtilities.is_checked(obj): return checked return unchecked if AXUtilities.is_radio_button(obj): unselected, selected = object_properties.RADIO_BUTTON_INDICATORS_SPEECH if AXUtilities.is_checked(obj): return selected return unselected if AXUtilities.is_link(obj): if AXUtilities.is_visited(obj): return object_properties.STATE_VISITED else: return object_properties.STATE_UNVISITED return '' def _getValue(self, obj): # Another case where we'll do this for now, and clean it up when # object presentation is refactored. return self._getState(obj) or self._getText(obj) ######################################################################### # # # Objects # # # ######################################################################### # All structural navigation objects have the following essential # characteristics: # # 1. Keybindings for goPrevious, goNext, and other such methods. # This is a dictionary. See _setUpHandlersAndBindings() for # supported values. But "previous", "next", and "list" are # typically what you'll need. # 2. A means of identification: MatchCriteria and optional predicate. # The MatchCriteria is required. For ATK implementations, AT-SPI2 # implements Collection. Applications and toolkits which implement # AT-SPI2 directly should provide the implementation because our # getting all objects via a tree dive is extremely non-performant. # The predicate is only needed if Collection lacks something we # need to identify the object is really the thing we want. Usually # the predicate is not needed and can remain undefined. # 3. A definition of how the object should be presented (both when # another instance of that object is found as well as when it is # not). This function should do the presentation. # 4. Details needed to populate the dialog with the object list is # presented. # # Convenience methods have been put into place whereby one can # create an object (FOO = "foo"), and then provide the following # methods: _fooBindings(), _fooPredicate(), _fooCriteria(), and # _fooPresentation(). With these in place, and with the object # FOO included among the StructuralNavigation.enabledTypes for # the script, the structural navigation object should be created # and set up automagically. At least that is the idea. :-) This # hopefully will also enable easy re-definition of existing # objects on a script-by-script basis. ######################## # # # Blockquotes # # # ######################## def _blockquoteBindings(self): bindings = {} prevDesc = cmdnames.BLOCKQUOTE_PREV bindings["previous"] = ["q", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.BLOCKQUOTE_NEXT bindings["next"] = ["q", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.BLOCKQUOTE_LIST bindings["list"] = ["q", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _blockquoteGetter(self, document, arg=None): return AXUtilities.find_all_block_quotes(document) def _blockquotePresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) else: full = messages.NO_MORE_BLOCKQUOTES brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _blockquoteDialogData(self): columnHeaders = [guilabels.SN_HEADER_BLOCKQUOTE] def rowData(obj): return [self._getText(obj)] return guilabels.SN_TITLE_BLOCKQUOTE, columnHeaders, rowData ######################## # # # Buttons # # # ######################## def _buttonBindings(self): bindings = {} prevDesc = cmdnames.BUTTON_PREV bindings["previous"] = ["b", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.BUTTON_NEXT bindings["next"] = ["b", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.BUTTON_LIST bindings["list"] = ["b", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _buttonGetter(self, document, arg=None): return AXUtilities.find_all_buttons(document) def _buttonPresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) else: full = messages.NO_MORE_BUTTONS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _buttonDialogData(self): columnHeaders = [guilabels.SN_HEADER_BUTTON] def rowData(obj): return [self._getText(obj)] return guilabels.SN_TITLE_BUTTON, columnHeaders, rowData ######################## # # # Check boxes # # # ######################## def _checkBoxBindings(self): bindings = {} prevDesc = cmdnames.CHECK_BOX_PREV bindings["previous"] = ["x", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.CHECK_BOX_NEXT bindings["next"] = ["x", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.CHECK_BOX_LIST bindings["list"] = ["x", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _checkBoxGetter(self, document, arg=None): return AXUtilities.find_all_check_boxes(document) def _checkBoxPresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) else: full = messages.NO_MORE_CHECK_BOXES brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _checkBoxDialogData(self): columnHeaders = [guilabels.SN_HEADER_CHECK_BOX] columnHeaders.append(guilabels.SN_HEADER_STATE) def rowData(obj): return [self._getLabel(obj), self._getState(obj)] return guilabels.SN_TITLE_CHECK_BOX, columnHeaders, rowData ######################## # # # Chunks/Large Objects # # # ######################## def _chunkBindings(self): bindings = {} prevDesc = cmdnames.LARGE_OBJECT_PREV bindings["previous"] = ["o", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.LARGE_OBJECT_NEXT bindings["next"] = ["o", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.LARGE_OBJECT_LIST bindings["list"] = ["o", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _chunkCriteria(self, arg=None): return AXCollection.create_match_rule(roles=self.OBJECT_ROLES + self.CONTAINER_ROLES) def _chunkPredicate(self, obj, arg=None): if AXUtilities.is_heading(obj): return True length = AXText.get_character_count(obj) if length < settings.largeObjectTextLength: return False string = AXText.get_all_text(obj) eocs = string.count(self._script.EMBEDDED_OBJECT_CHARACTER) if eocs/length < 0.05: return True return False def _chunkPresentation(self, obj, arg=None): if obj is not None: [newObj, characterOffset] = self._getCaretPosition(obj) self._setCaretPosition(newObj, characterOffset) self._presentObject(obj, 0) else: full = messages.NO_MORE_CHUNKS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _chunkDialogData(self): columnHeaders = [guilabels.SN_HEADER_OBJECT] columnHeaders.append(guilabels.SN_HEADER_ROLE) def rowData(obj): return [self._getText(obj), self._getRoleName(obj)] return guilabels.SN_TITLE_LARGE_OBJECT, columnHeaders, rowData ######################## # # # Combo Boxes # # # ######################## def _comboBoxBindings(self): bindings = {} prevDesc = cmdnames.COMBO_BOX_PREV bindings["previous"] = ["c", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.COMBO_BOX_NEXT bindings["next"] = ["c", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.COMBO_BOX_LIST bindings["list"] = ["c", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _comboBoxGetter(self, document, arg=None): return AXUtilities.find_all_combo_boxes(document) def _comboBoxPresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) else: full = messages.NO_MORE_COMBO_BOXES brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _comboBoxDialogData(self): columnHeaders = [guilabels.SN_HEADER_COMBO_BOX] columnHeaders.append(guilabels.SN_HEADER_SELECTED_ITEM) def rowData(obj): return [self._getLabel(obj), self._getText(obj)] return guilabels.SN_TITLE_COMBO_BOX, columnHeaders, rowData ######################## # # # Entries # # # ######################## def _entryBindings(self): bindings = {} prevDesc = cmdnames.ENTRY_PREV bindings["previous"] = ["e", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.ENTRY_NEXT bindings["next"] = ["e", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.ENTRY_LIST bindings["list"] = ["e", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _entryGetter(self, document, arg=None): def parent_is_not_editable(obj): parent = AXObject.get_parent(obj) return parent is not None and not AXUtilities.is_editable(parent) return AXUtilities.find_all_editable_objects(document, pred=parent_is_not_editable) def _entryPresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) else: full = messages.NO_MORE_ENTRIES brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _entryDialogData(self): columnHeaders = [guilabels.SN_HEADER_LABEL] columnHeaders.append(guilabels.SN_HEADER_TEXT) def rowData(obj): return [self._getLabel(obj), self._getText(obj)] return guilabels.SN_TITLE_ENTRY, columnHeaders, rowData ######################## # # # Form Fields # # # ######################## def _formFieldBindings(self): bindings = {} prevDesc = cmdnames.FORM_FIELD_PREV bindings["previous"] = ["f", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.FORM_FIELD_NEXT bindings["next"] = ["f", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.FORM_FIELD_LIST bindings["list"] = ["f", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _formFieldGetter(self, document, arg=None): def is_not_noneditable_doc_frame(obj): if AXUtilities.is_document_frame(obj): return AXUtilities.is_editable(obj) return True return AXUtilities.find_all_form_fields(document, pred=is_not_noneditable_doc_frame) def _formFieldPresentation(self, obj, arg=None): if obj is not None: if AXUtilities.is_text(obj) and AXObject.get_child_count(obj): obj = AXObject.get_child(obj, 0) [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) else: full = messages.NO_MORE_FORM_FIELDS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _formFieldDialogData(self): columnHeaders = [guilabels.SN_HEADER_LABEL] columnHeaders.append(guilabels.SN_HEADER_ROLE) columnHeaders.append(guilabels.SN_HEADER_VALUE) def rowData(obj): return [self._getLabel(obj), self._getRoleName(obj), self._getValue(obj)] return guilabels.SN_TITLE_FORM_FIELD, columnHeaders, rowData ######################## # # # Headings # # # ######################## def _headingBindings(self): bindings = {} prevDesc = cmdnames.HEADING_PREV bindings["previous"] = ["h", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.HEADING_NEXT bindings["next"] = ["h", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.HEADING_LIST bindings["list"] = ["h", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] prevAtLevelBindings = [] nextAtLevelBindings = [] listAtLevelBindings = [] minLevel, maxLevel = self._headingLevels() for i in range(minLevel, maxLevel + 1): prevDesc = cmdnames.HEADING_AT_LEVEL_PREV % i prevAtLevelBindings.append([str(i), keybindings.SHIFT_MODIFIER_MASK, prevDesc]) nextDesc = cmdnames.HEADING_AT_LEVEL_NEXT % i nextAtLevelBindings.append([str(i), keybindings.NO_MODIFIER_MASK, nextDesc]) listDesc = cmdnames.HEADING_AT_LEVEL_LIST %i listAtLevelBindings.append([str(i), keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc]) bindings["previousAtLevel"] = prevAtLevelBindings bindings["nextAtLevel"] = nextAtLevelBindings bindings["listAtLevel"] = listAtLevelBindings return bindings def _headingLevels(self): return [1, 6] def _headingGetter(self, document, arg=None): if arg is not None: return AXUtilities.find_all_headings_at_level(document, level=arg) return AXUtilities.find_all_headings(document) def _headingPresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) elif arg is None: full = messages.NO_MORE_HEADINGS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) else: full = messages.NO_MORE_HEADINGS_AT_LEVEL % arg brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _headingDialogData(self, arg=None): columnHeaders = [guilabels.SN_HEADER_HEADING] if not arg: title = guilabels.SN_TITLE_HEADING columnHeaders.append(guilabels.SN_HEADER_LEVEL) def rowData(obj): return [self._getText(obj), str(self._script.utilities.headingLevel(obj))] else: title = guilabels.SN_TITLE_HEADING_AT_LEVEL % arg def rowData(obj): return [self._getText(obj)] return title, columnHeaders, rowData ######################## # # # Iframes # # # ######################## def _iframeBindings(self): bindings = {} prevDesc = cmdnames.IFRAME_PREV bindings["previous"] = ["", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.IFRAME_NEXT bindings["next"] = ["", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.IFRAME_LIST bindings["list"] = ["", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _iframeGetter(self, document, arg=None): return AXUtilities.find_all_internal_frames(document) def _iframePresentation(self, obj, arg=None): if obj is not None: [newObj, characterOffset] = self._getCaretPosition(obj) self._setCaretPosition(newObj, characterOffset) self._presentObject(obj, 0) else: full = messages.NO_MORE_IFRAMES brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _iframeDialogData(self): columnHeaders = [guilabels.SN_HEADER_IFRAME] def rowData(obj): name = AXObject.get_name(obj) if not name and AXObject.get_child_count(obj): name = AXObject.get_name(AXObject.get_child(obj, 0)) return [name or self._getRoleName(obj)] return guilabels.SN_TITLE_IFRAME, columnHeaders, rowData ######################## # # # Images # # # ######################## def _imageBindings(self): bindings = {} prevDesc = cmdnames.IMAGE_PREV bindings["previous"] = ["g", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.IMAGE_NEXT bindings["next"] = ["g", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.IMAGE_LIST bindings["list"] = ["g", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _imageGetter(self, document, arg=None): return AXUtilities.find_all_images_and_image_maps(document) def _imagePresentation(self, obj, arg=None): if obj is not None: [newObj, characterOffset] = self._getCaretPosition(obj) self._setCaretPosition(newObj, characterOffset) self._presentObject(obj, 0) else: full = messages.NO_MORE_IMAGES brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _imageDialogData(self): columnHeaders = [guilabels.SN_HEADER_IMAGE] def rowData(obj): return [self._getText(obj) or self._getRoleName(obj)] return guilabels.SN_TITLE_IMAGE, columnHeaders, rowData ######################## # # # Landmarks # # # ######################## def _landmarkBindings(self): bindings = {} prevDesc = cmdnames.LANDMARK_PREV bindings["previous"] = ["m", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.LANDMARK_NEXT bindings["next"] = ["m", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.LANDMARK_LIST bindings["list"] = ["m", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _landmarkGetter(self, document, arg=None): return AXUtilities.find_all_landmarks(document) def _landmarkPresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._script.presentMessage(AXObject.get_name(obj)) self._presentLine(obj, characterOffset) else: full = messages.NO_LANDMARK_FOUND brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _landmarkDialogData(self): columnHeaders = [guilabels.SN_HEADER_LANDMARK] columnHeaders.append(guilabels.SN_HEADER_ROLE) def rowData(obj): return [AXObject.get_name(obj), self._getRoleName(obj)] return guilabels.SN_TITLE_LANDMARK, columnHeaders, rowData ######################## # # # Lists # # # ######################## def _listBindings(self): bindings = {} prevDesc = cmdnames.LIST_PREV bindings["previous"] = ["l", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.LIST_NEXT bindings["next"] = ["l", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.LIST_LIST bindings["list"] = ["l", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _listGetter(self, document, arg=None): results = AXUtilities.find_all_lists(document) results.extend(AXUtilities.find_all_description_lists(document)) return results def _listPresentation(self, obj, arg=None): if obj is not None: self._script.speakMessage(self._getListDescription(obj)) [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentLine(obj, characterOffset) else: full = messages.NO_MORE_LISTS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _listDialogData(self): columnHeaders = [guilabels.SN_HEADER_LIST] def rowData(obj): return [self._getText(obj)] return guilabels.SN_TITLE_LIST, columnHeaders, rowData ######################## # # # List Items # # # ######################## def _listItemBindings(self): bindings = {} prevDesc = cmdnames.LIST_ITEM_PREV bindings["previous"] = ["i", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.LIST_ITEM_NEXT bindings["next"] = ["i", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.LIST_ITEM_LIST bindings["list"] = ["i", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _listItemGetter(self, document, arg=None): results = AXUtilities.find_all_list_items(document) results.extend(AXUtilities.find_all_description_terms(document)) return results def _listItemPresentation(self, obj, arg=None): if obj is None: full = messages.NO_MORE_LIST_ITEMS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) return thisList = None priorList = None focus = focus_manager.getManager().get_locus_of_focus() if AXUtilities.is_list_item(obj): thisList = AXObject.find_ancestor(obj, AXUtilities.is_list) priorList = AXObject.find_ancestor(focus, AXUtilities.is_list) else: thisList = AXObject.find_ancestor(obj, AXUtilities.is_description_list) priorList = AXObject.find_ancestor(focus, AXUtilities.is_description_list) if thisList is not None and priorList != thisList: self._script.speakMessage(self._getListDescription(thisList)) [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentLine(obj, characterOffset) def _listItemDialogData(self): columnHeaders = [guilabels.SN_HEADER_LIST_ITEM] def rowData(obj): return [self._getText(obj)] return guilabels.SN_TITLE_LIST_ITEM, columnHeaders, rowData ######################## # # # Live Regions # # # ######################## def _liveRegionBindings(self): bindings = {} prevDesc = cmdnames.LIVE_REGION_PREV bindings["previous"] = ["d", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.LIVE_REGION_NEXT bindings["next"] = ["d", keybindings.NO_MODIFIER_MASK, nextDesc] desc = cmdnames.LIVE_REGION_LAST bindings["last"] = ["y", keybindings.NO_MODIFIER_MASK, desc] return bindings def _liveRegionGetter(self, document, arg=None): return AXUtilities.find_all_live_regions(document) def _liveRegionPresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) else: full = messages.NO_MORE_LIVE_REGIONS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) ######################## # # # Paragraphs # # # ######################## def _paragraphBindings(self): bindings = {} prevDesc = cmdnames.PARAGRAPH_PREV bindings["previous"] = ["p", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.PARAGRAPH_NEXT bindings["next"] = ["p", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.PARAGRAPH_LIST bindings["list"] = ["p", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _paragraphGetter(self, document, arg=None): def has_at_least_three_characters(obj): if AXUtilities.is_heading(obj): return True # We're choosing 3 characters as the minimum because some # paragraphs contain a single image or link and a text # of length 2: An embedded object character and a space. # We want to skip these. return AXText.get_character_count(obj) > 2 return AXUtilities.find_all_paragraphs(document, True, has_at_least_three_characters) def _paragraphPresentation(self, obj, arg=None): if obj is not None: [newObj, characterOffset] = self._getCaretPosition(obj) self._setCaretPosition(newObj, characterOffset) self._presentObject(obj, 0) else: full = messages.NO_MORE_PARAGRAPHS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _paragraphDialogData(self): columnHeaders = [guilabels.SN_HEADER_PARAGRAPH] def rowData(obj): return [self._getText(obj)] return guilabels.SN_TITLE_PARAGRAPH, columnHeaders, rowData ######################## # # # Radio Buttons # # # ######################## def _radioButtonBindings(self): bindings = {} prevDesc = cmdnames.RADIO_BUTTON_PREV bindings["previous"] = ["r", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.RADIO_BUTTON_NEXT bindings["next"] = ["r", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.RADIO_BUTTON_LIST bindings["list"] = ["r", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _radioButtonGetter(self, document, arg=None): return AXUtilities.find_all_radio_buttons(document) def _radioButtonPresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) else: full = messages.NO_MORE_RADIO_BUTTONS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _radioButtonDialogData(self): columnHeaders = [guilabels.SN_HEADER_RADIO_BUTTON] columnHeaders.append(guilabels.SN_HEADER_STATE) def rowData(obj): return [self._getLabel(obj), self._getState(obj)] return guilabels.SN_TITLE_RADIO_BUTTON, columnHeaders, rowData ######################## # # # Separators # # # ######################## def _separatorBindings(self): bindings = {} prevDesc = cmdnames.SEPARATOR_PREV bindings["previous"] = ["s", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.SEPARATOR_NEXT bindings["next"] = ["s", keybindings.NO_MODIFIER_MASK, nextDesc] return bindings def _separatorGetter(self, document, arg=None): return AXUtilities.find_all_separators(document) def _separatorPresentation(self, obj, arg=None): if obj is not None: [newObj, characterOffset] = self._getCaretPosition(obj) self._setCaretPosition(newObj, characterOffset) self._presentObject(obj, 0) else: full = messages.NO_MORE_SEPARATORS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) ######################## # # # Tables # # # ######################## def _tableBindings(self): bindings = {} prevDesc = cmdnames.TABLE_PREV bindings["previous"] = ["t", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.TABLE_NEXT bindings["next"] = ["t", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.TABLE_LIST bindings["list"] = ["t", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _tableGetter(self, document, arg=None): return AXUtilities.find_all_tables(document) def _tablePresentation(self, obj, arg=None): if obj is not None: caption = AXTable.get_caption(obj) if caption: self._script.presentMessage(self._script.utilities.displayedText(caption)) self._script.presentMessage(AXTable.get_table_description_for_presentation(obj)) cell = AXTable.get_cell_at(obj, 0, 0) if not cell: tokens = ["STRUCTURAL NAVIGATION: Broken table interface for", obj] debug.printTokens(debug.LEVEL_INFO, tokens, True) cell = AXObject.find_descendant(obj, AXUtilities.is_table_cell) if cell: tokens = ["STRUCTURAL NAVIGATION: Located", cell, "for first cell"] debug.printTokens(debug.LEVEL_INFO, tokens, True) self.lastTableCell = [0, 0] self._presentObject(cell, 0, priorObj=obj) [cell, characterOffset] = self._getCaretPosition(cell) self._setCaretPosition(cell, characterOffset) else: full = messages.NO_MORE_TABLES brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _tableDialogData(self): columnHeaders = [guilabels.SN_HEADER_CAPTION] columnHeaders.append(guilabels.SN_HEADER_DESCRIPTION) def rowData(obj): caption = AXTable.get_caption(obj) if caption: name = self._script.utilities.displayedText(caption) else: name = AXObject.get_name(obj) return [name, AXTable.get_table_description_for_presentation(obj)] return guilabels.SN_TITLE_TABLE, columnHeaders, rowData ######################## # # # Unvisited Links # # # ######################## def _unvisitedLinkBindings(self): bindings = {} prevDesc = cmdnames.UNVISITED_LINK_PREV bindings["previous"] = ["u", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.UNVISITED_LINK_NEXT bindings["next"] = ["u", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.UNVISITED_LINK_LIST bindings["list"] = ["u", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _unvisitedLinkGetter(self, document, arg=None): return AXUtilities.find_all_unvisited_links(document) def _unvisitedLinkPresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) else: full = messages.NO_MORE_UNVISITED_LINKS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _unvisitedLinkDialogData(self): columnHeaders = [guilabels.SN_HEADER_LINK] columnHeaders.append(guilabels.SN_HEADER_URI) def rowData(obj): return [self._getText(obj), AXHypertext.get_link_uri(obj)] return guilabels.SN_TITLE_UNVISITED_LINK, columnHeaders, rowData ######################## # # # Visited Links # # # ######################## def _visitedLinkBindings(self): bindings = {} prevDesc = cmdnames.VISITED_LINK_PREV bindings["previous"] = ["v", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.VISITED_LINK_NEXT bindings["next"] = ["v", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.VISITED_LINK_LIST bindings["list"] = ["v", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _visitedLinkGetter(self, document, arg=None): return AXUtilities.find_all_visited_links(document) def _visitedLinkPresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) else: full = messages.NO_MORE_VISITED_LINKS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _visitedLinkDialogData(self): columnHeaders = [guilabels.SN_HEADER_LINK] columnHeaders.append(guilabels.SN_HEADER_URI) def rowData(obj): return [self._getText(obj), AXHypertext.get_link_uri(obj)] return guilabels.SN_TITLE_VISITED_LINK, columnHeaders, rowData ######################## # # # Plain ol' Links # # # ######################## def _linkBindings(self): bindings = {} prevDesc = cmdnames.LINK_PREV bindings["previous"] = ["k", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.LINK_NEXT bindings["next"] = ["k", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.LINK_LIST bindings["list"] = ["k", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _linkGetter(self, document, arg=None): return AXUtilities.find_all_links(document) def _linkPresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) else: full = messages.NO_MORE_LINKS brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _linkDialogData(self): columnHeaders = [guilabels.SN_HEADER_LINK] columnHeaders.append(guilabels.SN_HEADER_STATE) columnHeaders.append(guilabels.SN_HEADER_URI) def rowData(obj): return [self._getText(obj), self._getState(obj), AXHypertext.get_link_uri(obj)] return guilabels.SN_TITLE_LINK, columnHeaders, rowData ######################## # # # Clickables # # # ######################## def _clickableBindings(self): bindings = {} prevDesc = cmdnames.CLICKABLE_PREV bindings["previous"] = ["a", keybindings.SHIFT_MODIFIER_MASK, prevDesc] nextDesc = cmdnames.CLICKABLE_NEXT bindings["next"] = ["a", keybindings.NO_MODIFIER_MASK, nextDesc] listDesc = cmdnames.CLICKABLE_LIST bindings["list"] = ["a", keybindings.SHIFT_ALT_MODIFIER_MASK, listDesc] return bindings def _clickableCriteria(self, arg=None): return AXCollection.create_match_rule( interfaces=["action"], interface_match_type=Atspi.CollectionMatchType.ANY) def _clickablePredicate(self, obj, arg=None): return self._script.utilities.isClickableElement(obj) def _clickablePresentation(self, obj, arg=None): if obj is not None: [obj, characterOffset] = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentObject(obj, characterOffset) elif not arg: full = messages.NO_MORE_CLICKABLES brief = messages.STRUCTURAL_NAVIGATION_NOT_FOUND self._script.presentMessage(full, brief) def _clickableDialogData(self): columnHeaders = [guilabels.SN_HEADER_CLICKABLE] columnHeaders.append(guilabels.SN_HEADER_ROLE) def rowData(obj): return [self._getText(obj), self._getRoleName(obj)] return guilabels.SN_TITLE_CLICKABLE, columnHeaders, rowData ######################## # # # Containers # # # ######################## def _containerBindings(self): bindings = {} desc = cmdnames.CONTAINER_START bindings["start"] = ["comma", keybindings.SHIFT_MODIFIER_MASK, desc] desc = cmdnames.CONTAINER_END bindings["end"] = ["comma", keybindings.NO_MODIFIER_MASK, desc] return bindings def _containerCriteria(self, arg=None): return AXCollection.create_match_rule(roles=self.CONTAINER_ROLES) def _containerPredicate(self, obj, arg=None): return self._isContainer(obj) def _containerPresentation(self, obj, arg=None, **kwargs): if obj is None: self._script.presentMessage(messages.CONTAINER_NOT_IN_A) return if kwargs.get("sameContainer"): self._script.presentMessage(messages.CONTAINER_END) characterOffset = arg if characterOffset is None: obj, characterOffset = self._getCaretPosition(obj) obj, characterOffset = self._setCaretPosition(obj, characterOffset) self._presentLine(obj, characterOffset)