%PDF- %PDF-
Direktori : /lib/python3/dist-packages/orca/ |
Current File : //lib/python3/dist-packages/orca/object_navigator.py |
# Orca # # Copyright 2023 The Orca Team # Author: Rynhardt Kruger <rynkruger@gmail.com> # # 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. """Provides ability to navigate objects hierarchically.""" __id__ = "$Id$" __version__ = "$Revision$" __date__ = "$Date$" __copyright__ = "Copyright (c) 2023 The Orca Team" __license__ = "LGPL" from . import cmdnames from . import debug from . import focus_manager from . import input_event from . import keybindings from . import messages from .ax_event_synthesizer import AXEventSynthesizer from .ax_object import AXObject from .ax_utilities import AXUtilities class ObjectNavigator: """Provides ability to navigate objects hierarchically.""" def __init__(self): self._navigator_focus = None self._last_navigator_focus = None self._last_locus_of_focus = None self._simplify = True self._handlers = self.get_handlers(True) self._bindings = keybindings.KeyBindings() def get_bindings(self, refresh=False, is_desktop=True): """Returns the object-navigator keybindings.""" if refresh: msg = "OBJECT NAVIGATOR: Refreshing bindings." debug.printMessage(debug.LEVEL_INFO, msg, True) self._setup_bindings() elif self._bindings.isEmpty(): self._setup_bindings() return self._bindings def get_handlers(self, refresh=False): """Returns the object-navigator handlers.""" if refresh: msg = "OBJECT NAVIGATOR: Refreshing handlers." debug.printMessage(debug.LEVEL_INFO, msg, True) self._setup_handlers() return self._handlers def _setup_bindings(self): """Sets up the object-navigator key bindings.""" self._bindings = keybindings.KeyBindings() self._bindings.add( keybindings.KeyBinding( "Up", keybindings.defaultModifierMask, keybindings.ORCA_CTRL_MODIFIER_MASK, self._handlers.get("object_navigator_up"))) self._bindings.add( keybindings.KeyBinding( "Down", keybindings.defaultModifierMask, keybindings.ORCA_CTRL_MODIFIER_MASK, self._handlers.get("object_navigator_down"))) self._bindings.add( keybindings.KeyBinding( "Right", keybindings.defaultModifierMask, keybindings.ORCA_CTRL_MODIFIER_MASK, self._handlers.get("object_navigator_next"))) self._bindings.add( keybindings.KeyBinding( "Left", keybindings.defaultModifierMask, keybindings.ORCA_CTRL_MODIFIER_MASK, self._handlers.get("object_navigator_previous"))) self._bindings.add( keybindings.KeyBinding( "Return", keybindings.defaultModifierMask, keybindings.ORCA_CTRL_MODIFIER_MASK, self._handlers.get("object_navigator_perform_action"))) self._bindings.add( keybindings.KeyBinding( "s", keybindings.defaultModifierMask, keybindings.ORCA_CTRL_MODIFIER_MASK, self._handlers.get("object_navigator_toggle_simplify"))) msg = "OBJECT NAVIGATOR: Bindings set up." debug.printMessage(debug.LEVEL_INFO, msg, True) def _setup_handlers(self): """Sets up the object-navigator input event handlers.""" self._handlers = {} self._handlers["object_navigator_up"] = \ input_event.InputEventHandler( self.up, cmdnames.NAVIGATOR_UP) self._handlers["object_navigator_down"] = \ input_event.InputEventHandler( self.down, cmdnames.NAVIGATOR_DOWN) self._handlers["object_navigator_next"] = \ input_event.InputEventHandler( self.next, cmdnames.NAVIGATOR_NEXT) self._handlers["object_navigator_previous"] = \ input_event.InputEventHandler( self.previous, cmdnames.NAVIGATOR_PREVIOUS) self._handlers["object_navigator_perform_action"] = \ input_event.InputEventHandler( self.perform_action, cmdnames.NAVIGATOR_PERFORM_ACTION) self._handlers["object_navigator_toggle_simplify"] = \ input_event.InputEventHandler( self.toggle_simplify, cmdnames.NAVIGATOR_TOGGLE_SIMPLIFIED) msg = "OBJECT NAVIGATOR: Handlers set up." debug.printMessage(debug.LEVEL_INFO, msg, True) def _include_in_simple_navigation(self, obj): """Returns True if obj should be included in simple navigation.""" return AXUtilities.is_paragraph(obj) def _exclude_from_simple_navigation(self, script, obj): """Returns True if obj should be excluded from simple navigation.""" if self._include_in_simple_navigation(obj): tokens = ["OBJECT NAVIGATOR: Not excluding", obj, ": explicit inclusion"] debug.printTokens(debug.LEVEL_INFO, tokens, True) return False # isLayoutOnly should catch things that really should be skipped. # # You do not want to exclude all sections because they may be focusable, e.g. # <div tabindex=0>foo</div> should not be excluded, despite the poor authoring. # # You do not want to exclude table cells and headers because it will make the # selectable items in tables non-navigable (e.g. the mail folders in Evolution) if script.utilities.isLayoutOnly(obj): tokens = ["OBJECT NAVIGATOR: Excluding", obj, ": is layout only"] debug.printTokens(debug.LEVEL_INFO, tokens, True) return True tokens = ["OBJECT NAVIGATOR: Not excluding", obj] debug.printTokens(debug.LEVEL_INFO, tokens, True) return False def _children(self, script, obj): """Returns a list of children for obj, taking simple navigation into account.""" if not AXObject.get_child_count(obj): return [] children = list(AXObject.iter_children(obj)) if not self._simplify: return children # Add the children of excluded objects to our list of children. functional_children = [] for child in children: if self._exclude_from_simple_navigation(script, child): functional_children.extend(self._children(script, child)) else: functional_children.append(child) return functional_children def _parent(self, script, obj): """Returns the parent for obj, taking simple navigation into account.""" parent = AXObject.get_parent(obj) if not self._simplify: return parent # The first non-excluded ancestor is the functional parent. while parent is not None and self._exclude_from_simple_navigation(script, parent): parent = AXObject.get_parent(parent) return parent def _set_navigator_focus(self, obj): """Changes the navigator focus, storing the previous focus.""" self._last_navigator_focus = self._navigator_focus self._navigator_focus = obj def update(self): """Updates the navigator focus to Orca's object of interest.""" mode, region = focus_manager.getManager().get_active_mode_and_object_of_interest() obj = region or focus_manager.getManager().get_locus_of_focus() if self._last_locus_of_focus == obj \ or (region is None and mode == focus_manager.FLAT_REVIEW): return self._navigator_focus = obj self._last_locus_of_focus = obj def present(self, script): """Presents the current navigator focus to the user.""" tokens = ["OBJECT NAVIGATOR: Presenting", self._navigator_focus] debug.printTokens(debug.LEVEL_INFO, tokens, True) focus_manager.getManager().emit_region_changed( self._navigator_focus, mode=focus_manager.OBJECT_NAVIGATOR) script.presentObject(self._navigator_focus, priorObj=self._last_navigator_focus) def up(self, script, event=None): """Moves the navigator focus to the parent of the current focus.""" self.update() parent = self._parent(script, self._navigator_focus) if parent is not None: self._set_navigator_focus(parent) self.present(script) else: script.presentMessage(messages.NAVIGATOR_NO_PARENT) def down(self, script, event=None): """Moves the navigator focus to the first child of the current focus.""" self.update() children = self._children(script, self._navigator_focus) if not children: script.presentMessage(messages.NAVIGATOR_NO_CHILDREN) return self._set_navigator_focus(children[0]) self.present(script) def next(self, script, event=None): """Moves the navigator focus to the next sibling of the current focus.""" self.update() parent = self._parent(script, self._navigator_focus) if parent is None: script.presentMessage(messages.NAVIGATOR_NO_NEXT) return siblings = self._children(script, parent) if self._navigator_focus in siblings: index = siblings.index(self._navigator_focus) if index < len(siblings) - 1: self._set_navigator_focus(siblings[index+1]) self.present(script) else: script.presentMessage(messages.NAVIGATOR_NO_NEXT) else: self._set_navigator_focus(parent) self.present(script) def previous(self, script, event=None): """Moves the navigator focus to the previous sibling of the current focus.""" self.update() parent = self._parent(script, self._navigator_focus) if parent is None: script.presentMessage(messages.NAVIGATOR_NO_PREVIOUS) return siblings = self._children(script, parent) if self._navigator_focus in siblings: index = siblings.index(self._navigator_focus) if index > 0: self._set_navigator_focus(siblings[index-1]) self.present(script) else: script.presentMessage(messages.NAVIGATOR_NO_PREVIOUS) else: self._set_navigator_focus(parent) self.present(script) def toggle_simplify(self, script, event=None): """Toggles simplified navigation.""" self._simplify = not self._simplify if self._simplify: script.presentMessage(messages.NAVIGATOR_SIMPLIFIED_ENABLED) else: script.presentMessage(messages.NAVIGATOR_SIMPLIFIED_DISABLED) return True def perform_action(self, script, event=None): """Attempts to click on the current focus.""" if AXEventSynthesizer.try_all_clickable_actions(self._navigator_focus): return True AXEventSynthesizer.click_object(self._navigator_focus, 1) return True _navigator = ObjectNavigator() def getNavigator(): """Returns the Object Navigator""" return _navigator