Wow, there was a lot of stuff left to do, that and bug fixes. I think we're pretty much back to a working state now. Will test and merge if I don't find anything.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# Orca
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
|
# Copyright (c) 2024 Stormux
|
||||||
# Copyright 2024 Igalia, S.L.
|
# Copyright 2024 Igalia, S.L.
|
||||||
# Copyright 2024 GNOME Foundation Inc.
|
# Copyright 2024 GNOME Foundation Inc.
|
||||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||||
@@ -18,6 +19,9 @@
|
|||||||
# License along with this library; if not, write to the
|
# License along with this library; if not, write to the
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||||
# Boston MA 02110-1301 USA.
|
# Boston MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
# Forked from Orca screen reader.
|
||||||
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Orca
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
|
# Copyright (c) 2024 Stormux
|
||||||
# Copyright 2024 Igalia, S.L.
|
# Copyright 2024 Igalia, S.L.
|
||||||
# Copyright 2024 GNOME Foundation Inc.
|
# Copyright 2024 GNOME Foundation Inc.
|
||||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||||
@@ -18,6 +19,9 @@
|
|||||||
# License along with this library; if not, write to the
|
# License along with this library; if not, write to the
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||||
# Boston MA 02110-1301 USA.
|
# Boston MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
# Forked from Orca screen reader.
|
||||||
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Orca
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
|
# Copyright (c) 2024 Stormux
|
||||||
# Copyright 2005-2008 Sun Microsystems Inc.
|
# Copyright 2005-2008 Sun Microsystems Inc.
|
||||||
# Copyright 2018-2023 Igalia, S.L.
|
# Copyright 2018-2023 Igalia, S.L.
|
||||||
#
|
#
|
||||||
@@ -17,6 +18,9 @@
|
|||||||
# License along with this library; if not, write to the
|
# License along with this library; if not, write to the
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||||
# Boston MA 02110-1301 USA.
|
# Boston MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
# Forked from Orca screen reader.
|
||||||
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Orca
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
|
# Copyright (c) 2024 Stormux
|
||||||
# Copyright 2024 Igalia, S.L.
|
# Copyright 2024 Igalia, S.L.
|
||||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||||
#
|
#
|
||||||
@@ -17,6 +18,9 @@
|
|||||||
# License along with this library; if not, write to the
|
# License along with this library; if not, write to the
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||||
# Boston MA 02110-1301 USA.
|
# Boston MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
# Forked from Orca screen reader.
|
||||||
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Orca
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
|
# Copyright (c) 2024 Stormux
|
||||||
# Copyright 2024 Igalia, S.L.
|
# Copyright 2024 Igalia, S.L.
|
||||||
# Copyright 2024 GNOME Foundation Inc.
|
# Copyright 2024 GNOME Foundation Inc.
|
||||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||||
@@ -18,6 +19,9 @@
|
|||||||
# License along with this library; if not, write to the
|
# License along with this library; if not, write to the
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||||
# Boston MA 02110-1301 USA.
|
# Boston MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
# Forked from Orca screen reader.
|
||||||
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
# pylint: disable=too-many-locals
|
# pylint: disable=too-many-locals
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Orca
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
|
# Copyright (c) 2024 Stormux
|
||||||
# Copyright 2024 Igalia, S.L.
|
# Copyright 2024 Igalia, S.L.
|
||||||
# Copyright 2024 GNOME Foundation Inc.
|
# Copyright 2024 GNOME Foundation Inc.
|
||||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||||
@@ -18,6 +19,9 @@
|
|||||||
# License along with this library; if not, write to the
|
# License along with this library; if not, write to the
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||||
# Boston MA 02110-1301 USA.
|
# Boston MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
# Forked from Orca screen reader.
|
||||||
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Orca
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
|
# Copyright (c) 2024 Stormux
|
||||||
# Copyright 2024 Igalia, S.L.
|
# Copyright 2024 Igalia, S.L.
|
||||||
# Copyright 2024 GNOME Foundation Inc.
|
# Copyright 2024 GNOME Foundation Inc.
|
||||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||||
@@ -18,6 +19,9 @@
|
|||||||
# License along with this library; if not, write to the
|
# License along with this library; if not, write to the
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||||
# Boston MA 02110-1301 USA.
|
# Boston MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
# Forked from Orca screen reader.
|
||||||
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Orca
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
|
# Copyright (c) 2024 Stormux
|
||||||
# Copyright 2024 Igalia, S.L.
|
# Copyright 2024 Igalia, S.L.
|
||||||
# Copyright 2024 GNOME Foundation Inc.
|
# Copyright 2024 GNOME Foundation Inc.
|
||||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||||
@@ -18,6 +19,9 @@
|
|||||||
# License along with this library; if not, write to the
|
# License along with this library; if not, write to the
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||||
# Boston MA 02110-1301 USA.
|
# Boston MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
# Forked from Orca screen reader.
|
||||||
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
|
|
||||||
|
|||||||
+21
-20
@@ -42,6 +42,9 @@ import signal
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version("Atspi", "2.0")
|
||||||
|
from gi.repository import Atspi
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
|
|
||||||
from . import brltablenames
|
from . import brltablenames
|
||||||
@@ -54,6 +57,7 @@ from . import settings_manager
|
|||||||
|
|
||||||
from .ax_event_synthesizer import AXEventSynthesizer
|
from .ax_event_synthesizer import AXEventSynthesizer
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
|
from .ax_hypertext import AXHypertext
|
||||||
from .cthulhu_platform import tablesdir
|
from .cthulhu_platform import tablesdir
|
||||||
|
|
||||||
_logger = logger.getLogger()
|
_logger = logger.getLogger()
|
||||||
@@ -537,10 +541,11 @@ class Component(Region):
|
|||||||
|
|
||||||
if cthulhu_state.activeScript and cthulhu_state.activeScript.utilities.\
|
if cthulhu_state.activeScript and cthulhu_state.activeScript.utilities.\
|
||||||
grabFocusBeforeRouting(self.accessible, offset):
|
grabFocusBeforeRouting(self.accessible, offset):
|
||||||
try:
|
if AXObject.supports_component(self.accessible):
|
||||||
self.accessible.queryComponent().grabFocus()
|
try:
|
||||||
except Exception:
|
Atspi.Component.grab_focus(self.accessible)
|
||||||
pass
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
if AXObject.do_action(self.accessible, 0):
|
if AXObject.do_action(self.accessible, 0):
|
||||||
return
|
return
|
||||||
@@ -747,22 +752,18 @@ class Text(Region):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
if getLinkMask and linkIndicator != settings.BRAILLE_UNDERLINE_NONE:
|
if getLinkMask and linkIndicator != settings.BRAILLE_UNDERLINE_NONE:
|
||||||
try:
|
if AXObject.supports_hypertext(self.accessible):
|
||||||
hyperText = self.accessible.queryHypertext()
|
for link in AXHypertext.get_all_links(self.accessible):
|
||||||
nLinks = hyperText.getNLinks()
|
start = AXHypertext.get_link_start_offset(link)
|
||||||
except Exception:
|
end = AXHypertext.get_link_end_offset(link)
|
||||||
nLinks = 0
|
if start < 0 or end < 0:
|
||||||
|
continue
|
||||||
n = 0
|
if self.lineOffset <= start:
|
||||||
while n < nLinks:
|
for i in range(start, end):
|
||||||
link = hyperText.getLink(n)
|
try:
|
||||||
if self.lineOffset <= link.startIndex:
|
regionMask[i] |= linkIndicator
|
||||||
for i in range(link.startIndex, link.endIndex):
|
except Exception:
|
||||||
try:
|
pass
|
||||||
regionMask[i] |= linkIndicator
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
n += 1
|
|
||||||
|
|
||||||
if attrIndicator:
|
if attrIndicator:
|
||||||
keys, enabledAttributes = script.utilities.stringToKeysAndDict(
|
keys, enabledAttributes = script.utilities.stringToKeysAndDict(
|
||||||
|
|||||||
@@ -500,7 +500,7 @@ class Context:
|
|||||||
self.container = None
|
self.container = None
|
||||||
self.focusObj = cthulhu.getActiveModeAndObjectOfInterest()[1] or cthulhu_state.locusOfFocus
|
self.focusObj = cthulhu.getActiveModeAndObjectOfInterest()[1] or cthulhu_state.locusOfFocus
|
||||||
self.topLevel = None
|
self.topLevel = None
|
||||||
self.bounds = 0, 0, 0, 0
|
self.bounds = Atspi.Rect()
|
||||||
|
|
||||||
frame, dialog = script.utilities.frameAndDialog(self.focusObj)
|
frame, dialog = script.utilities.frameAndDialog(self.focusObj)
|
||||||
if root is not None:
|
if root is not None:
|
||||||
@@ -513,9 +513,7 @@ class Context:
|
|||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if AXObject.supports_component(self.topLevel):
|
self.bounds = AXComponent.get_rect(self.topLevel)
|
||||||
rect = Atspi.Component.get_extents(self.topLevel, Atspi.CoordType.SCREEN)
|
|
||||||
self.bounds = rect.x, rect.y, rect.width, rect.height
|
|
||||||
except Exception:
|
except Exception:
|
||||||
tokens = ["ERROR: Exception getting extents of", self.topLevel]
|
tokens = ["ERROR: Exception getting extents of", self.topLevel]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
@@ -567,6 +565,7 @@ class Context:
|
|||||||
Returns a list of Zones for the visible text.
|
Returns a list of Zones for the visible text.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
cliprect = self._ensureRect(cliprect)
|
||||||
zones = []
|
zones = []
|
||||||
substrings = [(*m.span(), m.group(0)) for m in re.finditer(r"[^\ufffc]+", string)]
|
substrings = [(*m.span(), m.group(0)) for m in re.finditer(r"[^\ufffc]+", string)]
|
||||||
substrings = list(map(lambda x: (x[0] + startOffset, x[1] + startOffset, x[2]), substrings))
|
substrings = list(map(lambda x: (x[0] + startOffset, x[1] + startOffset, x[2]), substrings))
|
||||||
@@ -610,6 +609,7 @@ class Context:
|
|||||||
Returns a list of Zones.
|
Returns a list of Zones.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
cliprect = self._ensureRect(cliprect)
|
||||||
if not self.script.utilities.hasPresentableText(accessible):
|
if not self.script.utilities.hasPresentableText(accessible):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -716,6 +716,7 @@ class Context:
|
|||||||
def getZonesFromAccessible(self, accessible, cliprect):
|
def getZonesFromAccessible(self, accessible, cliprect):
|
||||||
"""Returns a list of Zones for the given accessible."""
|
"""Returns a list of Zones for the given accessible."""
|
||||||
|
|
||||||
|
cliprect = self._ensureRect(cliprect)
|
||||||
try:
|
try:
|
||||||
if AXObject.supports_component(accessible):
|
if AXObject.supports_component(accessible):
|
||||||
rect = Atspi.Component.get_extents(accessible, Atspi.CoordType.SCREEN)
|
rect = Atspi.Component.get_extents(accessible, Atspi.CoordType.SCREEN)
|
||||||
@@ -760,6 +761,25 @@ class Context:
|
|||||||
|
|
||||||
return AXObject.find_ancestor(child, lambda x: x == parent)
|
return AXObject.find_ancestor(child, lambda x: x == parent)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _ensureRect(rect):
|
||||||
|
if rect is None:
|
||||||
|
return Atspi.Rect()
|
||||||
|
if hasattr(rect, "x") and hasattr(rect, "y") \
|
||||||
|
and hasattr(rect, "width") and hasattr(rect, "height"):
|
||||||
|
return rect
|
||||||
|
|
||||||
|
try:
|
||||||
|
x, y, width, height = rect
|
||||||
|
except Exception:
|
||||||
|
return Atspi.Rect()
|
||||||
|
|
||||||
|
newRect = Atspi.Rect()
|
||||||
|
newRect.x = x
|
||||||
|
newRect.y = y
|
||||||
|
newRect.width = width
|
||||||
|
newRect.height = height
|
||||||
|
return newRect
|
||||||
def setCurrentToZoneWithObject(self, obj):
|
def setCurrentToZoneWithObject(self, obj):
|
||||||
"""Attempts to set the current zone to obj, if obj is in the current context."""
|
"""Attempts to set the current zone to obj, if obj is in the current context."""
|
||||||
|
|
||||||
@@ -822,6 +842,7 @@ class Context:
|
|||||||
|
|
||||||
if boundingbox is None:
|
if boundingbox is None:
|
||||||
boundingbox = self.bounds
|
boundingbox = self.bounds
|
||||||
|
boundingbox = self._ensureRect(boundingbox)
|
||||||
|
|
||||||
objs = self.script.utilities.getOnScreenObjects(root, boundingbox)
|
objs = self.script.utilities.getOnScreenObjects(root, boundingbox)
|
||||||
tokens = ["FLAT REVIEW:", len(objs), "on-screen objects found for", root]
|
tokens = ["FLAT REVIEW:", len(objs), "on-screen objects found for", root]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Orca
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
|
# Copyright (c) 2024 Stormux
|
||||||
# Copyright 2005-2008 Sun Microsystems Inc.
|
# Copyright 2005-2008 Sun Microsystems Inc.
|
||||||
# Copyright 2016-2023 Igalia, S.L.
|
# Copyright 2016-2023 Igalia, S.L.
|
||||||
#
|
#
|
||||||
@@ -19,6 +20,9 @@
|
|||||||
# License along with this library; if not, write to the
|
# License along with this library; if not, write to the
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||||
# Boston MA 02110-1301 USA.
|
# Boston MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
# Forked from Orca screen reader.
|
||||||
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ from . import object_properties
|
|||||||
from . import settings
|
from . import settings
|
||||||
from . import settings_manager
|
from . import settings_manager
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
|
from .ax_text import AXText
|
||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
from .ax_utilities_relation import AXUtilitiesRelation
|
from .ax_utilities_relation import AXUtilitiesRelation
|
||||||
|
|
||||||
@@ -526,14 +527,9 @@ class Generator:
|
|||||||
exists. Otherwise, an empty array is returned.
|
exists. Otherwise, an empty array is returned.
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
try:
|
description = AXObject.get_image_description(obj)
|
||||||
image = obj.queryImage()
|
if description and len(description):
|
||||||
except NotImplementedError:
|
result.append(description)
|
||||||
pass
|
|
||||||
else:
|
|
||||||
description = image.imageDescription
|
|
||||||
if description and len(description):
|
|
||||||
result.append(description)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
#####################################################################
|
#####################################################################
|
||||||
@@ -1194,7 +1190,10 @@ class Generator:
|
|||||||
if not (AXUtilities.is_table_cell(rad) and AXObject.get_child_count(rad)):
|
if not (AXUtilities.is_table_cell(rad) and AXObject.get_child_count(rad)):
|
||||||
return self._generateDisplayedText(rad, **args)
|
return self._generateDisplayedText(rad, **args)
|
||||||
|
|
||||||
content = set([self._script.utilities.displayedText(x).strip() for x in rad])
|
content = {
|
||||||
|
(AXObject.get_name(x) or AXText.get_all_text(x)).strip()
|
||||||
|
for x in AXObject.iter_children(rad)
|
||||||
|
}
|
||||||
rv = " ".join(filter(lambda x: x, content))
|
rv = " ".join(filter(lambda x: x, content))
|
||||||
if not rv:
|
if not rv:
|
||||||
return self._generateDisplayedText(rad, **args)
|
return self._generateDisplayedText(rad, **args)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Orca
|
#!/usr/bin/env python3
|
||||||
#
|
#
|
||||||
|
# Copyright (c) 2024 Stormux
|
||||||
# Copyright 2024 Igalia, S.L.
|
# Copyright 2024 Igalia, S.L.
|
||||||
# Copyright 2024 GNOME Foundation Inc.
|
# Copyright 2024 GNOME Foundation Inc.
|
||||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||||
@@ -18,6 +19,9 @@
|
|||||||
# License along with this library; if not, write to the
|
# License along with this library; if not, write to the
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||||
# Boston MA 02110-1301 USA.
|
# Boston MA 02110-1301 USA.
|
||||||
|
#
|
||||||
|
# Forked from Orca screen reader.
|
||||||
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ from gi.repository import Atspi
|
|||||||
|
|
||||||
from . import debug
|
from . import debug
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
|
from .ax_table import AXTable
|
||||||
from .ax_text import AXText
|
from .ax_text import AXText
|
||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
|
|
||||||
@@ -236,16 +237,16 @@ class LabelInference:
|
|||||||
return extents
|
return extents
|
||||||
|
|
||||||
if not (extents[2] and extents[3]):
|
if not (extents[2] and extents[3]):
|
||||||
try:
|
if not AXObject.supports_component(obj):
|
||||||
ext = obj.queryComponent().getExtents(0)
|
tokens = ["LABEL INFERENCE:", obj, "does not support the component interface"]
|
||||||
except NotImplementedError:
|
|
||||||
tokens = ["LABEL INFERENCE:", obj, "does not implement the component interface"]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
except Exception:
|
|
||||||
tokens = ["LABEL INFERENCE: Exception getting extents for", obj]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
else:
|
else:
|
||||||
extents = ext.x, ext.y, ext.width, ext.height
|
try:
|
||||||
|
ext = Atspi.Component.get_extents(obj, Atspi.CoordType.SCREEN)
|
||||||
|
extents = ext.x, ext.y, ext.width, ext.height
|
||||||
|
except Exception:
|
||||||
|
tokens = ["LABEL INFERENCE: Exception getting extents for", obj]
|
||||||
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
|
|
||||||
self._extentsCache[(hash(obj), startOffset, endOffset)] = extents
|
self._extentsCache[(hash(obj), startOffset, endOffset)] = extents
|
||||||
return extents
|
return extents
|
||||||
@@ -484,11 +485,12 @@ class LabelInference:
|
|||||||
if rowindex < 0 or colindex < 0:
|
if rowindex < 0 or colindex < 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
iface = table.queryTable()
|
rows = AXTable.get_row_count(table, prefer_attribute=False)
|
||||||
if rowindex >= iface.nRows or colindex >= iface.nColumns:
|
cols = AXTable.get_column_count(table, prefer_attribute=False)
|
||||||
|
if rowindex >= rows or colindex >= cols:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return table.queryTable().getAccessibleAt(rowindex, colindex)
|
return AXTable.get_cell_at(table, rowindex, colindex)
|
||||||
|
|
||||||
def _getCellFromRow(self, row, colindex):
|
def _getCellFromRow(self, row, colindex):
|
||||||
if 0 <= colindex < AXObject.get_child_count(row):
|
if 0 <= colindex < AXObject.get_child_count(row):
|
||||||
@@ -587,12 +589,12 @@ class LabelInference:
|
|||||||
# as a functional label. Therefore, see if this table looks like a grid
|
# as a functional label. Therefore, see if this table looks like a grid
|
||||||
# of widgets with the functional labels in the first row.
|
# of widgets with the functional labels in the first row.
|
||||||
|
|
||||||
try:
|
rows = AXTable.get_row_count(grid, prefer_attribute=False)
|
||||||
table = grid.queryTable()
|
cols = AXTable.get_column_count(grid, prefer_attribute=False)
|
||||||
except NotImplementedError:
|
if rows <= 0 or cols <= 0:
|
||||||
return None, []
|
return None, []
|
||||||
|
|
||||||
firstRow = [table.getAccessibleAt(0, i) for i in range(table.nColumns)]
|
firstRow = [AXTable.get_cell_at(grid, 0, i) for i in range(cols)]
|
||||||
if not firstRow or list(filter(self._isWidget, firstRow)):
|
if not firstRow or list(filter(self._isWidget, firstRow)):
|
||||||
return None, []
|
return None, []
|
||||||
|
|
||||||
@@ -604,7 +606,7 @@ class LabelInference:
|
|||||||
return False
|
return False
|
||||||
return not AXUtilities.have_same_role(AXObject.get_child(x, 0), obj)
|
return not AXUtilities.have_same_role(AXObject.get_child(x, 0), obj)
|
||||||
|
|
||||||
cells = [table.getAccessibleAt(i, colindex) for i in range(1, table.nRows)]
|
cells = [AXTable.get_cell_at(grid, i, colindex) for i in range(1, rows)]
|
||||||
if list(filter(isMatch, cells)):
|
if list(filter(isMatch, cells)):
|
||||||
return None, []
|
return None, []
|
||||||
|
|
||||||
|
|||||||
@@ -546,8 +546,11 @@ class MouseReviewer:
|
|||||||
if coordType is None:
|
if coordType is None:
|
||||||
coordType = Atspi.CoordType.SCREEN
|
coordType = Atspi.CoordType.SCREEN
|
||||||
|
|
||||||
|
if not AXObject.supports_component(obj):
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return obj.queryComponent().contains(x, y, coordType)
|
return Atspi.Component.contains(obj, x, y, coordType)
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -557,12 +560,15 @@ class MouseReviewer:
|
|||||||
if coordType is None:
|
if coordType is None:
|
||||||
coordType = Atspi.CoordType.SCREEN
|
coordType = Atspi.CoordType.SCREEN
|
||||||
|
|
||||||
|
if not AXObject.supports_component(obj):
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
extents = obj.queryComponent().getExtents(coordType)
|
extents = Atspi.Component.get_extents(obj, coordType)
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return list(extents) == list(bounds)
|
return [extents.x, extents.y, extents.width, extents.height] == list(bounds)
|
||||||
|
|
||||||
def _accessible_window_at_point(self, pX, pY):
|
def _accessible_window_at_point(self, pX, pY):
|
||||||
"""Returns the accessible window at the specified coordinates."""
|
"""Returns the accessible window at the specified coordinates."""
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from cthulhu import settings_manager
|
|||||||
from cthulhu import cthulhu_state
|
from cthulhu import cthulhu_state
|
||||||
from cthulhu import ax_object
|
from cthulhu import ax_object
|
||||||
from cthulhu.ax_text import AXText
|
from cthulhu.ax_text import AXText
|
||||||
|
from cthulhu.ax_value import AXValue
|
||||||
from cthulhu import ax_utilities
|
from cthulhu import ax_utilities
|
||||||
from cthulhu.ax_utilities_state import AXUtilitiesState
|
from cthulhu.ax_utilities_state import AXUtilitiesState
|
||||||
from cthulhu.plugins.AIAssistant.ai_providers import create_provider
|
from cthulhu.plugins.AIAssistant.ai_providers import create_provider
|
||||||
@@ -758,12 +759,8 @@ class AIAssistant(Plugin):
|
|||||||
"""Get value from an accessibility object."""
|
"""Get value from an accessibility object."""
|
||||||
try:
|
try:
|
||||||
if ax_object.AXObject.supports_value(obj):
|
if ax_object.AXObject.supports_value(obj):
|
||||||
try:
|
value = AXValue.get_current_value(obj)
|
||||||
value_iface = obj.queryValue()
|
return str(value) if value is not None else ""
|
||||||
if value_iface:
|
|
||||||
return str(value_iface.currentValue) or ""
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -818,18 +815,16 @@ class AIAssistant(Plugin):
|
|||||||
def _get_object_position(self, obj):
|
def _get_object_position(self, obj):
|
||||||
"""Get position and size information from an accessibility object."""
|
"""Get position and size information from an accessibility object."""
|
||||||
try:
|
try:
|
||||||
if hasattr(obj, 'queryComponent'):
|
if ax_object.AXObject.supports_component(obj):
|
||||||
component = obj.queryComponent()
|
extents = Atspi.Component.get_extents(obj, Atspi.CoordType.SCREEN)
|
||||||
if component:
|
return {
|
||||||
extents = component.getExtents(Atspi.CoordType.SCREEN)
|
'x': extents.x,
|
||||||
return {
|
'y': extents.y,
|
||||||
'x': extents.x,
|
'width': extents.width,
|
||||||
'y': extents.y,
|
'height': extents.height
|
||||||
'width': extents.width,
|
}
|
||||||
'height': extents.height
|
|
||||||
}
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting object position: {e}")
|
logger.error(f"Error getting object position: {e}")
|
||||||
return None
|
return None
|
||||||
@@ -1166,21 +1161,25 @@ class AIAssistant(Plugin):
|
|||||||
actions = []
|
actions = []
|
||||||
|
|
||||||
# Check for AT-SPI action interface
|
# Check for AT-SPI action interface
|
||||||
try:
|
if ax_object.AXObject.supports_action(obj):
|
||||||
if hasattr(obj, 'queryAction'):
|
try:
|
||||||
action_iface = obj.queryAction()
|
action_count = Atspi.Action.get_n_actions(obj)
|
||||||
if action_iface:
|
except Exception:
|
||||||
action_count = action_iface.get_nActions()
|
action_count = 0
|
||||||
for i in range(action_count):
|
for i in range(action_count):
|
||||||
action_name = action_iface.getName(i)
|
try:
|
||||||
action_desc = action_iface.getDescription(i)
|
action_name = Atspi.Action.get_name(obj, i)
|
||||||
actions.append({
|
except Exception:
|
||||||
'name': action_name or '',
|
action_name = ""
|
||||||
'description': action_desc or '',
|
try:
|
||||||
'index': i
|
action_desc = Atspi.Action.get_description(obj, i)
|
||||||
})
|
except Exception:
|
||||||
except:
|
action_desc = ""
|
||||||
pass
|
actions.append({
|
||||||
|
'name': action_name or '',
|
||||||
|
'description': action_desc or '',
|
||||||
|
'index': i
|
||||||
|
})
|
||||||
|
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
|
|||||||
@@ -114,8 +114,10 @@ class SpeechHistory(Plugin):
|
|||||||
|
|
||||||
self._create_window()
|
self._create_window()
|
||||||
self._window.show_all()
|
self._window.show_all()
|
||||||
|
if self._treeView and len(self._filterModel) > 0:
|
||||||
if self._filterEntry:
|
self._treeView.grab_focus()
|
||||||
|
self._treeView.set_cursor(Gtk.TreePath.new_first())
|
||||||
|
elif self._filterEntry:
|
||||||
self._filterEntry.grab_focus()
|
self._filterEntry.grab_focus()
|
||||||
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Window shown", True)
|
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Window shown", True)
|
||||||
@@ -137,6 +139,8 @@ class SpeechHistory(Plugin):
|
|||||||
|
|
||||||
mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
||||||
|
|
||||||
|
self._filterText = ""
|
||||||
|
|
||||||
# Filter row
|
# Filter row
|
||||||
filterRow = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
filterRow = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
||||||
filterLabel = Gtk.Label(label="_Filter:")
|
filterLabel = Gtk.Label(label="_Filter:")
|
||||||
@@ -153,7 +157,7 @@ class SpeechHistory(Plugin):
|
|||||||
mainBox.pack_start(filterRow, False, False, 0)
|
mainBox.pack_start(filterRow, False, False, 0)
|
||||||
|
|
||||||
# List
|
# List
|
||||||
self._listStore = Gtk.ListStore(int, str)
|
self._listStore = Gtk.ListStore(str)
|
||||||
self._filterModel = self._listStore.filter_new()
|
self._filterModel = self._listStore.filter_new()
|
||||||
self._filterModel.set_visible_func(self._filter_visible_func)
|
self._filterModel.set_visible_func(self._filter_visible_func)
|
||||||
|
|
||||||
@@ -163,16 +167,10 @@ class SpeechHistory(Plugin):
|
|||||||
selection = self._treeView.get_selection()
|
selection = self._treeView.get_selection()
|
||||||
selection.set_mode(Gtk.SelectionMode.SINGLE)
|
selection.set_mode(Gtk.SelectionMode.SINGLE)
|
||||||
|
|
||||||
idxRenderer = Gtk.CellRendererText()
|
|
||||||
idxColumn = Gtk.TreeViewColumn("Item", idxRenderer, text=0)
|
|
||||||
idxColumn.set_resizable(False)
|
|
||||||
idxColumn.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
|
|
||||||
self._treeView.append_column(idxColumn)
|
|
||||||
|
|
||||||
textRenderer = Gtk.CellRendererText()
|
textRenderer = Gtk.CellRendererText()
|
||||||
textRenderer.set_property("wrap-width", 640)
|
textRenderer.set_property("wrap-width", 640)
|
||||||
textRenderer.set_property("wrap-mode", 2) # Pango.WrapMode.WORD_CHAR
|
textRenderer.set_property("wrap-mode", 2) # Pango.WrapMode.WORD_CHAR
|
||||||
textColumn = Gtk.TreeViewColumn("Spoken Text", textRenderer, text=1)
|
textColumn = Gtk.TreeViewColumn("Spoken Text", textRenderer, text=0)
|
||||||
textColumn.set_resizable(True)
|
textColumn.set_resizable(True)
|
||||||
textColumn.set_expand(True)
|
textColumn.set_expand(True)
|
||||||
self._treeView.append_column(textColumn)
|
self._treeView.append_column(textColumn)
|
||||||
@@ -221,7 +219,7 @@ class SpeechHistory(Plugin):
|
|||||||
if not filterText:
|
if not filterText:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
spokenText = model[treeIter][1] or ""
|
spokenText = model[treeIter][0] or ""
|
||||||
return spokenText.lower().startswith(filterText)
|
return spokenText.lower().startswith(filterText)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR in filter func: {e}", True)
|
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR in filter func: {e}", True)
|
||||||
@@ -236,11 +234,21 @@ class SpeechHistory(Plugin):
|
|||||||
self._listStore.clear()
|
self._listStore.clear()
|
||||||
|
|
||||||
items = speech_history.get_items()
|
items = speech_history.get_items()
|
||||||
for idx, item in enumerate(items, start=1):
|
debug.printMessage(
|
||||||
self._listStore.append([idx, item])
|
debug.LEVEL_INFO,
|
||||||
|
f"SpeechHistory: Retrieved {len(items)} items (paused={speech_history.is_capture_paused()})",
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
for item in items:
|
||||||
|
self._listStore.append([item])
|
||||||
|
|
||||||
if self._filterModel:
|
if self._filterModel:
|
||||||
self._filterModel.refilter()
|
self._filterModel.refilter()
|
||||||
|
debug.printMessage(
|
||||||
|
debug.LEVEL_INFO,
|
||||||
|
f"SpeechHistory: Filtered items visible={len(self._filterModel)} filter='{self._filterText}'",
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
|
||||||
if selectFirst and self._treeView and len(self._filterModel) > 0:
|
if selectFirst and self._treeView and len(self._filterModel) > 0:
|
||||||
selection = self._treeView.get_selection()
|
selection = self._treeView.get_selection()
|
||||||
@@ -259,7 +267,7 @@ class SpeechHistory(Plugin):
|
|||||||
if not treeIter:
|
if not treeIter:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return model[treeIter][1]
|
return model[treeIter][0]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR getting selection: {e}", True)
|
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR getting selection: {e}", True)
|
||||||
logger.exception("Error getting selected speech history item")
|
logger.exception("Error getting selected speech history item")
|
||||||
@@ -403,4 +411,3 @@ class SpeechHistory(Plugin):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR presenting message: {e}", True)
|
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR presenting message: {e}", True)
|
||||||
logger.exception("Error presenting message from SpeechHistory")
|
logger.exception("Error presenting message from SpeechHistory")
|
||||||
|
|
||||||
|
|||||||
+373
-637
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
import cthulhu.scripts.default as default
|
import cthulhu.scripts.default as default
|
||||||
import cthulhu.cthulhu_state as cthulhu_state
|
import cthulhu.cthulhu_state as cthulhu_state
|
||||||
|
from cthulhu.ax_value import AXValue
|
||||||
|
|
||||||
from .script_utilities import Utilities
|
from .script_utilities import Utilities
|
||||||
|
|
||||||
@@ -47,8 +48,7 @@ class Script(default.Script):
|
|||||||
def onValueChanged(self, event):
|
def onValueChanged(self, event):
|
||||||
obj = event.source
|
obj = event.source
|
||||||
if self.utilities.isSeekSlider(obj):
|
if self.utilities.isSeekSlider(obj):
|
||||||
value = obj.queryValue()
|
current_value = int(AXValue.get_current_value(obj)) / 1000
|
||||||
current_value = int(value.currentValue)/1000
|
|
||||||
if current_value in range(self._last_seek_value, self._last_seek_value + 4):
|
if current_value in range(self._last_seek_value, self._last_seek_value + 4):
|
||||||
if self.utilities.isSameObject(obj, cthulhu_state.locusOfFocus):
|
if self.utilities.isSameObject(obj, cthulhu_state.locusOfFocus):
|
||||||
self.updateBraille(obj)
|
self.updateBraille(obj)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
import cthulhu.script_utilities as script_utilities
|
import cthulhu.script_utilities as script_utilities
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
|
from cthulhu.ax_value import AXValue
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
|
|
||||||
|
|
||||||
@@ -56,9 +57,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
if not self.isSeekSlider(obj):
|
if not self.isSeekSlider(obj):
|
||||||
return script_utilities.Utilities.textForValue(self, obj)
|
return script_utilities.Utilities.textForValue(self, obj)
|
||||||
|
|
||||||
try:
|
if not AXObject.supports_value(obj):
|
||||||
value = obj.queryValue()
|
|
||||||
except NotImplementedError:
|
|
||||||
return script_utilities.Utilities.textForValue(self, obj)
|
return script_utilities.Utilities.textForValue(self, obj)
|
||||||
else:
|
|
||||||
return self._formatDuration(int(value.currentValue)/1000)
|
return self._formatDuration(int(AXValue.get_current_value(obj)) / 1000)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import cthulhu.scripts.default as default
|
|||||||
import cthulhu.settings as settings
|
import cthulhu.settings as settings
|
||||||
import cthulhu.settings_manager as settings_manager
|
import cthulhu.settings_manager as settings_manager
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
|
from cthulhu.ax_value import AXValue
|
||||||
|
|
||||||
_settingsManager = settings_manager.getManager()
|
_settingsManager = settings_manager.getManager()
|
||||||
|
|
||||||
@@ -47,10 +48,9 @@ _settingsManager = settings_manager.getManager()
|
|||||||
|
|
||||||
class Script(default.Script):
|
class Script(default.Script):
|
||||||
def onValueChanged(self, event):
|
def onValueChanged(self, event):
|
||||||
try:
|
if AXObject.supports_value(event.source):
|
||||||
ivalue = event.source.queryValue()
|
value = int(AXValue.get_current_value(event.source))
|
||||||
value = int(ivalue.currentValue)
|
else:
|
||||||
except NotImplementedError:
|
|
||||||
value = -1
|
value = -1
|
||||||
|
|
||||||
if value >= 0:
|
if value >= 0:
|
||||||
@@ -63,10 +63,9 @@ class Script(default.Script):
|
|||||||
def onNameChanged(self, event):
|
def onNameChanged(self, event):
|
||||||
"""Callback for object:property-change:accessible-name events."""
|
"""Callback for object:property-change:accessible-name events."""
|
||||||
|
|
||||||
try:
|
if AXObject.supports_value(event.source):
|
||||||
ivalue = event.source.queryValue()
|
value = AXValue.get_current_value(event.source)
|
||||||
value = ivalue.currentValue
|
else:
|
||||||
except NotImplementedError:
|
|
||||||
value = -1
|
value = -1
|
||||||
|
|
||||||
message = ""
|
message = ""
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ from gi.repository import Atspi
|
|||||||
import cthulhu.debug as debug
|
import cthulhu.debug as debug
|
||||||
import cthulhu.script_utilities as script_utilities
|
import cthulhu.script_utilities as script_utilities
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
|
from cthulhu.ax_table import AXTable
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
from cthulhu.ax_utilities_relation import AXUtilitiesRelation
|
from cthulhu.ax_utilities_relation import AXUtilitiesRelation
|
||||||
|
|
||||||
@@ -83,18 +84,18 @@ class Utilities(script_utilities.Utilities):
|
|||||||
return script_utilities.Utilities.childNodes(self, obj)
|
return script_utilities.Utilities.childNodes(self, obj)
|
||||||
|
|
||||||
parent = AXObject.get_parent(obj)
|
parent = AXObject.get_parent(obj)
|
||||||
try:
|
table = AXTable.get_table(parent)
|
||||||
table = parent.queryTable()
|
if table is None:
|
||||||
except Exception:
|
return []
|
||||||
|
|
||||||
|
if not AXUtilities.is_expanded(obj):
|
||||||
return []
|
return []
|
||||||
else:
|
|
||||||
if not AXUtilities.is_expanded(obj):
|
|
||||||
return []
|
|
||||||
|
|
||||||
nodes = []
|
nodes = []
|
||||||
index = self.cellIndex(obj)
|
row, col = AXTable.get_cell_coordinates(obj, prefer_attribute=False)
|
||||||
row = table.getRowAtIndex(index)
|
if row < 0 or col < 0:
|
||||||
col = table.getColumnAtIndex(index + 1)
|
return []
|
||||||
|
col += 1
|
||||||
nodeLevel = self.nodeLevel(obj)
|
nodeLevel = self.nodeLevel(obj)
|
||||||
|
|
||||||
# Candidates will be in the rows beneath the current row.
|
# Candidates will be in the rows beneath the current row.
|
||||||
@@ -102,8 +103,10 @@ class Utilities(script_utilities.Utilities):
|
|||||||
# soon as the node level of a candidate is equal or less
|
# soon as the node level of a candidate is equal or less
|
||||||
# than our current level.
|
# than our current level.
|
||||||
#
|
#
|
||||||
for i in range(row+1, table.nRows):
|
for i in range(row + 1, AXTable.get_row_count(table, prefer_attribute=False)):
|
||||||
cell = table.getAccessibleAt(i, col)
|
cell = AXTable.get_cell_at(table, i, col)
|
||||||
|
if not cell:
|
||||||
|
continue
|
||||||
nodeCell = AXObject.get_previous_sibling(cell)
|
nodeCell = AXObject.get_previous_sibling(cell)
|
||||||
nodeOf = AXUtilitiesRelation.get_is_node_child_of(nodeCell)
|
nodeOf = AXUtilitiesRelation.get_is_node_child_of(nodeCell)
|
||||||
if not nodeOf:
|
if not nodeOf:
|
||||||
@@ -135,9 +138,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
|
|
||||||
obj = AXObject.get_previous_sibling(obj)
|
obj = AXObject.get_previous_sibling(obj)
|
||||||
parent = AXObject.get_parent(obj)
|
parent = AXObject.get_parent(obj)
|
||||||
try:
|
if AXTable.get_table(parent) is None:
|
||||||
parent.queryTable()
|
|
||||||
except Exception:
|
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
nodes = []
|
nodes = []
|
||||||
|
|||||||
@@ -856,7 +856,10 @@ class Script(default.Script):
|
|||||||
if isinstance(cthulhu_state.lastInputEvent, input_event.MouseButtonEvent):
|
if isinstance(cthulhu_state.lastInputEvent, input_event.MouseButtonEvent):
|
||||||
x = cthulhu_state.lastInputEvent.x
|
x = cthulhu_state.lastInputEvent.x
|
||||||
y = cthulhu_state.lastInputEvent.y
|
y = cthulhu_state.lastInputEvent.y
|
||||||
weToggledIt = obj.queryComponent().contains(x, y, 0)
|
if AXObject.supports_component(obj):
|
||||||
|
weToggledIt = Atspi.Component.contains(obj, x, y, Atspi.CoordType.SCREEN)
|
||||||
|
else:
|
||||||
|
weToggledIt = False
|
||||||
elif AXUtilities.is_focused(obj):
|
elif AXUtilities.is_focused(obj):
|
||||||
weToggledIt = True
|
weToggledIt = True
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import cthulhu.messages as messages
|
|||||||
import cthulhu.cthulhu_state as cthulhu_state
|
import cthulhu.cthulhu_state as cthulhu_state
|
||||||
import cthulhu.script_utilities as script_utilities
|
import cthulhu.script_utilities as script_utilities
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
|
from cthulhu.ax_table import AXTable
|
||||||
from cthulhu.ax_selection import AXSelection
|
from cthulhu.ax_selection import AXSelection
|
||||||
from cthulhu.ax_text import AXText
|
from cthulhu.ax_text import AXText
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
@@ -149,15 +150,11 @@ class Utilities(script_utilities.Utilities):
|
|||||||
if table is not None and not AXUtilities.is_table(table):
|
if table is not None and not AXUtilities.is_table(table):
|
||||||
table = AXObject.get_parent(table)
|
table = AXObject.get_parent(table)
|
||||||
|
|
||||||
try:
|
table = AXTable.get_table(table)
|
||||||
iTable = table.queryTable()
|
if table is None:
|
||||||
except Exception:
|
|
||||||
return -1, -1, None
|
return -1, -1, None
|
||||||
|
|
||||||
index = self.cellIndex(cell)
|
row, column = AXTable.get_cell_coordinates(cell, prefer_attribute=False)
|
||||||
row = iTable.getRowAtIndex(index)
|
|
||||||
column = iTable.getColumnAtIndex(index)
|
|
||||||
|
|
||||||
return row, column, table
|
return row, column, table
|
||||||
|
|
||||||
def rowHeadersForCell(self, obj):
|
def rowHeadersForCell(self, obj):
|
||||||
@@ -195,13 +192,12 @@ class Utilities(script_utilities.Utilities):
|
|||||||
getColHeader = \
|
getColHeader = \
|
||||||
getColHeader and objCol!= self._script.pointOfReference.get("lastColumn")
|
getColHeader and objCol!= self._script.pointOfReference.get("lastColumn")
|
||||||
|
|
||||||
parentTable = table.queryTable()
|
|
||||||
rowHeader, colHeader = None, None
|
rowHeader, colHeader = None, None
|
||||||
if getColHeader:
|
if getColHeader:
|
||||||
colHeader = parentTable.getAccessibleAt(headersRow, objCol)
|
colHeader = AXTable.get_cell_at(table, headersRow, objCol)
|
||||||
|
|
||||||
if getRowHeader:
|
if getRowHeader:
|
||||||
rowHeader = parentTable.getAccessibleAt(objRow, headersCol)
|
rowHeader = AXTable.get_cell_at(table, objRow, headersCol)
|
||||||
|
|
||||||
return rowHeader, colHeader
|
return rowHeader, colHeader
|
||||||
|
|
||||||
@@ -631,16 +627,13 @@ class Utilities(script_utilities.Utilities):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def _getCellNameForCoordinates(self, obj, row, col, includeContents=False):
|
def _getCellNameForCoordinates(self, obj, row, col, includeContents=False):
|
||||||
try:
|
if not AXObject.supports_table(obj):
|
||||||
table = obj.queryTable()
|
|
||||||
except Exception:
|
|
||||||
tokens = ["SOFFICE: Exception querying Table interface of", obj]
|
tokens = ["SOFFICE: Exception querying Table interface of", obj]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
cell = AXTable.get_cell_at(obj, row, col)
|
||||||
cell = table.getAccessibleAt(row, col)
|
if not cell:
|
||||||
except Exception:
|
|
||||||
tokens = [f"SOFFICE: Exception getting cell ({row},{col}) of", obj]
|
tokens = [f"SOFFICE: Exception getting cell ({row},{col}) of", obj]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
return
|
return
|
||||||
@@ -742,9 +735,9 @@ class Utilities(script_utilities.Utilities):
|
|||||||
if not (AXObject.supports_table(obj) and AXObject.supports_selection(obj)):
|
if not (AXObject.supports_table(obj) and AXObject.supports_selection(obj)):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
table = obj.queryTable()
|
cols = set(AXTable.get_selected_columns(obj))
|
||||||
cols = set(table.getSelectedColumns())
|
rows = set(AXTable.get_selected_rows(obj))
|
||||||
rows = set(table.getSelectedRows())
|
total_cols = AXTable.get_column_count(obj, prefer_attribute=False)
|
||||||
|
|
||||||
selectedCols = sorted(cols.difference(set(self._calcSelectedColumns)))
|
selectedCols = sorted(cols.difference(set(self._calcSelectedColumns)))
|
||||||
unselectedCols = sorted(set(self._calcSelectedColumns).difference(cols))
|
unselectedCols = sorted(set(self._calcSelectedColumns).difference(cols))
|
||||||
@@ -767,11 +760,11 @@ class Utilities(script_utilities.Utilities):
|
|||||||
self._calcSelectedColumns = list(cols)
|
self._calcSelectedColumns = list(cols)
|
||||||
self._calcSelectedRows = list(rows)
|
self._calcSelectedRows = list(rows)
|
||||||
|
|
||||||
if len(cols) == table.nColumns:
|
if len(cols) == total_cols:
|
||||||
self._script.speakMessage(messages.DOCUMENT_SELECTED_ALL)
|
self._script.speakMessage(messages.DOCUMENT_SELECTED_ALL)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if not len(cols) and len(unselectedCols) == table.nColumns:
|
if not len(cols) and len(unselectedCols) == total_cols:
|
||||||
self._script.speakMessage(messages.DOCUMENT_UNSELECTED_ALL)
|
self._script.speakMessage(messages.DOCUMENT_UNSELECTED_ALL)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -345,10 +345,12 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
|
|||||||
result = []
|
result = []
|
||||||
if AXObject.supports_text(obj):
|
if AXObject.supports_text(obj):
|
||||||
objectText = self._script.utilities.substring(obj, 0, -1)
|
objectText = self._script.utilities.substring(obj, 0, -1)
|
||||||
try:
|
extents = None
|
||||||
extents = obj.queryComponent().getExtents(Atspi.CoordType.SCREEN)
|
if AXObject.supports_component(obj):
|
||||||
except Exception:
|
try:
|
||||||
extents = None
|
extents = Atspi.Component.get_extents(obj, Atspi.CoordType.SCREEN)
|
||||||
|
except Exception:
|
||||||
|
extents = None
|
||||||
|
|
||||||
if extents is not None:
|
if extents is not None:
|
||||||
tooLongCount = 0
|
tooLongCount = 0
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ __license__ = "LGPL"
|
|||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version('Atspi', '2.0')
|
gi.require_version('Atspi', '2.0')
|
||||||
|
gi.require_version('Gdk', '3.0')
|
||||||
from gi.repository import Atspi
|
from gi.repository import Atspi
|
||||||
|
from gi.repository import Gdk
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
@@ -49,6 +51,7 @@ import cthulhu.debug as debug
|
|||||||
import cthulhu.find as find
|
import cthulhu.find as find
|
||||||
import cthulhu.flat_review as flat_review
|
import cthulhu.flat_review as flat_review
|
||||||
import cthulhu.input_event as input_event
|
import cthulhu.input_event as input_event
|
||||||
|
import cthulhu.input_event_manager as input_event_manager
|
||||||
import cthulhu.keybindings as keybindings
|
import cthulhu.keybindings as keybindings
|
||||||
import cthulhu.messages as messages
|
import cthulhu.messages as messages
|
||||||
import cthulhu.cthulhu as cthulhu
|
import cthulhu.cthulhu as cthulhu
|
||||||
@@ -62,6 +65,7 @@ import cthulhu.sound as sound
|
|||||||
import cthulhu.speech as speech
|
import cthulhu.speech as speech
|
||||||
import cthulhu.speechserver as speechserver
|
import cthulhu.speechserver as speechserver
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
|
from cthulhu.ax_value import AXValue
|
||||||
from cthulhu.ax_text import AXText
|
from cthulhu.ax_text import AXText
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
from cthulhu.ax_utilities_relation import AXUtilitiesRelation
|
from cthulhu.ax_utilities_relation import AXUtilitiesRelation
|
||||||
@@ -126,6 +130,7 @@ class Script(script.Script):
|
|||||||
self._sayAllIsInterrupted = False
|
self._sayAllIsInterrupted = False
|
||||||
self._sayAllContexts = []
|
self._sayAllContexts = []
|
||||||
self.grab_ids = []
|
self.grab_ids = []
|
||||||
|
self._modifierGrabIds = []
|
||||||
|
|
||||||
if app:
|
if app:
|
||||||
Atspi.Accessible.set_cache_mask(
|
Atspi.Accessible.set_cache_mask(
|
||||||
@@ -585,6 +590,7 @@ class Script(script.Script):
|
|||||||
for b in bound:
|
for b in bound:
|
||||||
for id in cthulhu.addKeyGrab(b):
|
for id in cthulhu.addKeyGrab(b):
|
||||||
self.grab_ids.append(id)
|
self.grab_ids.append(id)
|
||||||
|
self._addModifierGrabs()
|
||||||
|
|
||||||
def removeKeyGrabs(self):
|
def removeKeyGrabs(self):
|
||||||
""" Removes this script's AT-SPI key grabs. """
|
""" Removes this script's AT-SPI key grabs. """
|
||||||
@@ -593,6 +599,35 @@ class Script(script.Script):
|
|||||||
for id in self.grab_ids:
|
for id in self.grab_ids:
|
||||||
cthulhu.removeKeyGrab(id)
|
cthulhu.removeKeyGrab(id)
|
||||||
self.grab_ids = []
|
self.grab_ids = []
|
||||||
|
self._removeModifierGrabs()
|
||||||
|
|
||||||
|
def _addModifierGrabs(self):
|
||||||
|
if cthulhu_state.device is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._modifierGrabIds:
|
||||||
|
return
|
||||||
|
|
||||||
|
manager = input_event_manager.get_manager()
|
||||||
|
for modifier in settings.cthulhuModifierKeys:
|
||||||
|
if modifier not in ["Insert", "KP_Insert"]:
|
||||||
|
continue
|
||||||
|
keyval = Gdk.keyval_from_name(modifier)
|
||||||
|
keycode = keybindings.getKeycode(modifier)
|
||||||
|
if not keyval or not keycode:
|
||||||
|
continue
|
||||||
|
grabId = manager.add_grab_for_modifier(modifier, keyval, keycode)
|
||||||
|
if grabId != -1:
|
||||||
|
self._modifierGrabIds.append((modifier, grabId))
|
||||||
|
|
||||||
|
def _removeModifierGrabs(self):
|
||||||
|
if not self._modifierGrabIds:
|
||||||
|
return
|
||||||
|
|
||||||
|
manager = input_event_manager.get_manager()
|
||||||
|
for modifier, grabId in self._modifierGrabIds:
|
||||||
|
manager.remove_grab_for_modifier(modifier, grabId)
|
||||||
|
self._modifierGrabIds = []
|
||||||
|
|
||||||
def refreshKeyGrabs(self):
|
def refreshKeyGrabs(self):
|
||||||
""" Refreshes the enabled key grabs for this script. """
|
""" Refreshes the enabled key grabs for this script. """
|
||||||
@@ -1837,17 +1872,12 @@ class Script(script.Script):
|
|||||||
obj = event.source
|
obj = event.source
|
||||||
role = AXObject.get_role(obj)
|
role = AXObject.get_role(obj)
|
||||||
|
|
||||||
try:
|
if not AXObject.supports_value(obj):
|
||||||
value = obj.queryValue()
|
|
||||||
currentValue = value.currentValue
|
|
||||||
except NotImplementedError:
|
|
||||||
tokens = ["DEFAULT:", obj, "doesn't implement AtspiValue"]
|
tokens = ["DEFAULT:", obj, "doesn't implement AtspiValue"]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
return
|
return
|
||||||
except Exception:
|
|
||||||
tokens = ["DEFAULT: Exception getting current value for", obj]
|
currentValue = AXValue.get_current_value(obj)
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
return
|
|
||||||
|
|
||||||
if "oldValue" in self.pointOfReference \
|
if "oldValue" in self.pointOfReference \
|
||||||
and (currentValue == self.pointOfReference["oldValue"]):
|
and (currentValue == self.pointOfReference["oldValue"]):
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ __date__ = "$Date$"
|
|||||||
__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
|
__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
|
||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version("Atspi", "2.0")
|
||||||
|
from gi.repository import Atspi
|
||||||
|
|
||||||
import cthulhu.script_utilities as script_utilities
|
import cthulhu.script_utilities as script_utilities
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
@@ -81,15 +85,15 @@ class Utilities(script_utilities.Utilities):
|
|||||||
# negatives.
|
# negatives.
|
||||||
#
|
#
|
||||||
if AXUtilities.is_label(obj1) and AXUtilities.is_label(obj2):
|
if AXUtilities.is_label(obj1) and AXUtilities.is_label(obj2):
|
||||||
try:
|
if AXObject.supports_component(obj1) and AXObject.supports_component(obj2):
|
||||||
ext1 = obj1.queryComponent().getExtents(0)
|
try:
|
||||||
ext2 = obj2.queryComponent().getExtents(0)
|
ext1 = Atspi.Component.get_extents(obj1, Atspi.CoordType.SCREEN)
|
||||||
except Exception:
|
ext2 = Atspi.Component.get_extents(obj2, Atspi.CoordType.SCREEN)
|
||||||
pass
|
if ext1.x == ext2.x and ext1.y == ext2.y \
|
||||||
else:
|
and ext1.width == ext2.width and ext1.height == ext2.height:
|
||||||
if ext1.x == ext2.x and ext1.y == ext2.y \
|
return True
|
||||||
and ext1.width == ext2.width and ext1.height == ext2.height:
|
except Exception:
|
||||||
return True
|
pass
|
||||||
|
|
||||||
# In java applications, TRANSIENT state is missing for tree items
|
# In java applications, TRANSIENT state is missing for tree items
|
||||||
# (fix for bug #352250)
|
# (fix for bug #352250)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ import cthulhu.cthulhu_state as cthulhu_state
|
|||||||
import cthulhu.speech as speech
|
import cthulhu.speech as speech
|
||||||
import cthulhu.structural_navigation as structural_navigation
|
import cthulhu.structural_navigation as structural_navigation
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
|
from cthulhu.ax_hypertext import AXHypertext
|
||||||
from cthulhu.ax_text import AXText
|
from cthulhu.ax_text import AXText
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
|
|
||||||
@@ -613,14 +614,11 @@ class Script(default.Script):
|
|||||||
# just done with the current object. If we're still in SayAll, we do
|
# just done with the current object. If we're still in SayAll, we do
|
||||||
# not want to set the caret (and hence set focus) in a link we just
|
# not want to set the caret (and hence set focus) in a link we just
|
||||||
# passed by.
|
# passed by.
|
||||||
try:
|
if AXObject.supports_hypertext(obj):
|
||||||
hypertext = obj.queryHypertext()
|
links = AXHypertext.get_all_links(obj)
|
||||||
except NotImplementedError:
|
if [link for link in links
|
||||||
pass
|
if AXHypertext.get_link_start_offset(link)
|
||||||
else:
|
<= offset <= AXHypertext.get_link_end_offset(link)]:
|
||||||
linkCount = hypertext.getNLinks()
|
|
||||||
links = [hypertext.getLink(x) for x in range(linkCount)]
|
|
||||||
if [link for link in links if link.startIndex <= offset <= link.endIndex]:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
cthulhu.emitRegionChanged(obj, offset, mode=cthulhu.SAY_ALL)
|
cthulhu.emitRegionChanged(obj, offset, mode=cthulhu.SAY_ALL)
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import cthulhu.keybindings as keybindings
|
|||||||
import cthulhu.cthulhu as cthulhu
|
import cthulhu.cthulhu as cthulhu
|
||||||
import cthulhu.cthulhu_state as cthulhu_state
|
import cthulhu.cthulhu_state as cthulhu_state
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
|
from cthulhu.ax_hypertext import AXHypertext
|
||||||
from cthulhu.ax_text import AXText
|
from cthulhu.ax_text import AXText
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
|
|
||||||
@@ -142,9 +143,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
if not AXObject.supports_text(obj):
|
if not AXObject.supports_text(obj):
|
||||||
return [(obj, 0, 1, '')]
|
return [(obj, 0, 1, '')]
|
||||||
|
|
||||||
try:
|
if not AXObject.supports_hypertext(obj):
|
||||||
htext = obj.queryHypertext()
|
|
||||||
except (AttributeError, NotImplementedError):
|
|
||||||
return [(obj, 0, 1, '')]
|
return [(obj, 0, 1, '')]
|
||||||
|
|
||||||
string = AXText.get_all_text(obj)
|
string = AXText.get_all_text(obj)
|
||||||
@@ -179,10 +178,11 @@ class Utilities(script_utilities.Utilities):
|
|||||||
offsets = [x for x in offsets if start <= x < end]
|
offsets = [x for x in offsets if start <= x < end]
|
||||||
|
|
||||||
objects = []
|
objects = []
|
||||||
try:
|
objs = []
|
||||||
objs = [obj[htext.getLinkIndex(offset)] for offset in offsets]
|
for offset in offsets:
|
||||||
except Exception:
|
if child := AXHypertext.get_child_at_offset(obj, offset):
|
||||||
objs = []
|
objs.append(child)
|
||||||
|
|
||||||
ranges = [self.getHyperlinkRange(x) for x in objs]
|
ranges = [self.getHyperlinkRange(x) for x in objs]
|
||||||
for i, (first, last) in enumerate(ranges):
|
for i, (first, last) in enumerate(ranges):
|
||||||
objects.append((obj, start, first, string[start:first]))
|
objects.append((obj, start, first, string[start:first]))
|
||||||
|
|||||||
@@ -212,7 +212,10 @@ class Utilities(script_utilities.Utilities):
|
|||||||
if not text:
|
if not text:
|
||||||
return x, y
|
return x, y
|
||||||
|
|
||||||
objBox = obj.queryComponent().getExtents(coordType)
|
if not AXObject.supports_component(obj):
|
||||||
|
return x, y
|
||||||
|
|
||||||
|
objBox = Atspi.Component.get_extents(obj, coordType)
|
||||||
stringBox = Atspi.Text.get_range_extents(
|
stringBox = Atspi.Text.get_range_extents(
|
||||||
obj, 0, AXText.get_character_count(obj), coordType)
|
obj, 0, AXText.get_character_count(obj), coordType)
|
||||||
if self.intersection(objBox, stringBox) != (0, 0, 0, 0):
|
if self.intersection(objBox, stringBox) != (0, 0, 0, 0):
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ __copyright__ = "Copyright (c) 2005-2009 Sun Microsystems Inc." \
|
|||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import gi
|
||||||
|
gi.require_version("Atspi", "2.0")
|
||||||
|
from gi.repository import Atspi
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
from cthulhu import caret_navigation
|
from cthulhu import caret_navigation
|
||||||
@@ -40,6 +44,7 @@ from cthulhu import keybindings
|
|||||||
from cthulhu import debug
|
from cthulhu import debug
|
||||||
from cthulhu import guilabels
|
from cthulhu import guilabels
|
||||||
from cthulhu import input_event
|
from cthulhu import input_event
|
||||||
|
from cthulhu import input_event_manager
|
||||||
from cthulhu import liveregions
|
from cthulhu import liveregions
|
||||||
from cthulhu import messages
|
from cthulhu import messages
|
||||||
from cthulhu import cthulhu
|
from cthulhu import cthulhu
|
||||||
@@ -1002,8 +1007,7 @@ class Script(default.Script):
|
|||||||
|
|
||||||
document = self.utilities.getTopLevelDocumentForObject(obj)
|
document = self.utilities.getTopLevelDocumentForObject(obj)
|
||||||
obj, offset = self.utilities.getCaretContext(documentFrame=document)
|
obj, offset = self.utilities.getCaretContext(documentFrame=document)
|
||||||
keyString, mods = self.utilities.lastKeyAndModifiers()
|
if input_event_manager.get_manager().last_event_was_right():
|
||||||
if keyString == "Right":
|
|
||||||
offset -= 1
|
offset -= 1
|
||||||
|
|
||||||
wordContents = self.utilities.getWordContentsAtOffset(obj, offset, useCache=True)
|
wordContents = self.utilities.getWordContentsAtOffset(obj, offset, useCache=True)
|
||||||
@@ -1317,8 +1321,8 @@ class Script(default.Script):
|
|||||||
if not obj:
|
if not obj:
|
||||||
return
|
return
|
||||||
|
|
||||||
if AXUtilities.is_focusable(obj):
|
if AXUtilities.is_focusable(obj) and AXObject.supports_component(obj):
|
||||||
obj.queryComponent().grabFocus()
|
Atspi.Component.grab_focus(obj)
|
||||||
|
|
||||||
contents = self.utilities.get_objectContentsAtOffset(obj, offset)
|
contents = self.utilities.get_objectContentsAtOffset(obj, offset)
|
||||||
self.utilities.setCaretPosition(obj, offset)
|
self.utilities.setCaretPosition(obj, offset)
|
||||||
@@ -2449,6 +2453,10 @@ class Script(default.Script):
|
|||||||
msg = "WEB: Event believed to be browser UI page switch"
|
msg = "WEB: Event believed to be browser UI page switch"
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
if event.detail1:
|
if event.detail1:
|
||||||
|
# Work around stale cache when switching tabs.
|
||||||
|
AXObject.clear_cache(event.source, False, "Work around Chromium page switch.")
|
||||||
|
AXUtilities.clear_all_cache_now(reason=msg)
|
||||||
|
self.utilities.clearCaretContext()
|
||||||
self.presentObject(event.source, priorObj=cthulhu_state.locusOfFocus, interrupt=True)
|
self.presentObject(event.source, priorObj=cthulhu_state.locusOfFocus, interrupt=True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import urllib
|
|||||||
|
|
||||||
from cthulhu import debug
|
from cthulhu import debug
|
||||||
from cthulhu import input_event
|
from cthulhu import input_event
|
||||||
|
from cthulhu import input_event_manager
|
||||||
from cthulhu import messages
|
from cthulhu import messages
|
||||||
from cthulhu import cthulhu
|
from cthulhu import cthulhu
|
||||||
from cthulhu import cthulhu_state
|
from cthulhu import cthulhu_state
|
||||||
@@ -48,11 +49,14 @@ from cthulhu import script_utilities
|
|||||||
from cthulhu import script_manager
|
from cthulhu import script_manager
|
||||||
from cthulhu import settings_manager
|
from cthulhu import settings_manager
|
||||||
from cthulhu.ax_collection import AXCollection
|
from cthulhu.ax_collection import AXCollection
|
||||||
|
from cthulhu.ax_component import AXComponent
|
||||||
from cthulhu.ax_document import AXDocument
|
from cthulhu.ax_document import AXDocument
|
||||||
|
from cthulhu.ax_hypertext import AXHypertext
|
||||||
from cthulhu.ax_object import AXObject
|
from cthulhu.ax_object import AXObject
|
||||||
from cthulhu.ax_text import AXText
|
from cthulhu.ax_text import AXText
|
||||||
from cthulhu.ax_utilities import AXUtilities
|
from cthulhu.ax_utilities import AXUtilities
|
||||||
from cthulhu.ax_utilities_relation import AXUtilitiesRelation
|
from cthulhu.ax_utilities_relation import AXUtilitiesRelation
|
||||||
|
from cthulhu import speech_and_verbosity_manager
|
||||||
|
|
||||||
_scriptManager = script_manager.get_manager()
|
_scriptManager = script_manager.get_manager()
|
||||||
_settingsManager = settings_manager.getManager()
|
_settingsManager = settings_manager.getManager()
|
||||||
@@ -307,6 +311,16 @@ class Utilities(script_utilities.Utilities):
|
|||||||
documents = list(filter(AXUtilities.is_showing, documents))
|
documents = list(filter(AXUtilities.is_showing, documents))
|
||||||
if len(documents) == 1:
|
if len(documents) == 1:
|
||||||
return documents[0]
|
return documents[0]
|
||||||
|
|
||||||
|
# If multiple documents are showing (e.g., multi-tab browser), use the
|
||||||
|
# locus of focus to determine which document is currently active.
|
||||||
|
if documents:
|
||||||
|
focusDoc = self.getTopLevelDocumentForObject(cthulhu_state.locusOfFocus)
|
||||||
|
if focusDoc in documents:
|
||||||
|
tokens = ["WEB: Multiple showing documents, using focus-based document:", focusDoc]
|
||||||
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
|
return focusDoc
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def documentFrame(self, obj=None):
|
def documentFrame(self, obj=None):
|
||||||
@@ -356,11 +370,13 @@ class Utilities(script_utilities.Utilities):
|
|||||||
return AXUtilities.is_focusable(obj)
|
return AXUtilities.is_focusable(obj)
|
||||||
|
|
||||||
def grabFocus(self, obj):
|
def grabFocus(self, obj):
|
||||||
try:
|
if not AXObject.supports_component(obj):
|
||||||
obj.queryComponent().grabFocus()
|
tokens = ["WEB:", obj, "does not support the component interface"]
|
||||||
except NotImplementedError:
|
|
||||||
tokens = ["WEB:", obj, "does not implement the component interface"]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
Atspi.Component.grab_focus(obj)
|
||||||
except Exception:
|
except Exception:
|
||||||
tokens = ["WEB: Exception grabbing focus on", obj]
|
tokens = ["WEB: Exception grabbing focus on", obj]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
@@ -611,10 +627,8 @@ class Utilities(script_utilities.Utilities):
|
|||||||
|
|
||||||
nextobj, nextoffset = self.findNextCaretInOrder(obj, offset)
|
nextobj, nextoffset = self.findNextCaretInOrder(obj, offset)
|
||||||
if skipSpace:
|
if skipSpace:
|
||||||
text = self.queryNonEmptyText(nextobj)
|
while nextobj and AXText.get_character_at_offset(nextobj, nextoffset)[0].isspace():
|
||||||
while text and AXText.get_substring(nextobj, nextoffset, nextoffset + 1) in [" ", "\xa0"]:
|
|
||||||
nextobj, nextoffset = self.findNextCaretInOrder(nextobj, nextoffset)
|
nextobj, nextoffset = self.findNextCaretInOrder(nextobj, nextoffset)
|
||||||
text = self.queryNonEmptyText(nextobj)
|
|
||||||
|
|
||||||
return nextobj, nextoffset
|
return nextobj, nextoffset
|
||||||
|
|
||||||
@@ -624,10 +638,8 @@ class Utilities(script_utilities.Utilities):
|
|||||||
|
|
||||||
prevobj, prevoffset = self.findPreviousCaretInOrder(obj, offset)
|
prevobj, prevoffset = self.findPreviousCaretInOrder(obj, offset)
|
||||||
if skipSpace:
|
if skipSpace:
|
||||||
text = self.queryNonEmptyText(prevobj)
|
while prevobj and AXText.get_character_at_offset(prevobj, prevoffset)[0].isspace():
|
||||||
while text and AXText.get_substring(prevobj, prevoffset, prevoffset + 1) in [" ", "\xa0"]:
|
|
||||||
prevobj, prevoffset = self.findPreviousCaretInOrder(prevobj, prevoffset)
|
prevobj, prevoffset = self.findPreviousCaretInOrder(prevobj, prevoffset)
|
||||||
text = self.queryNonEmptyText(prevobj)
|
|
||||||
|
|
||||||
return prevobj, prevoffset
|
return prevobj, prevoffset
|
||||||
|
|
||||||
@@ -689,51 +701,25 @@ class Utilities(script_utilities.Utilities):
|
|||||||
if not obj:
|
if not obj:
|
||||||
return [0, 0, 0, 0]
|
return [0, 0, 0, 0]
|
||||||
|
|
||||||
result = [0, 0, 0, 0]
|
if AXObject.supports_text(obj) and 0 <= startOffset < endOffset:
|
||||||
if AXObject.supports_text(obj):
|
rect = AXText.get_range_rect(obj, startOffset, endOffset)
|
||||||
try:
|
result = [rect.x, rect.y, rect.width, rect.height]
|
||||||
char_count = AXText.get_character_count(obj)
|
if not (result[0] and result[1] and result[2] == 0 and result[3] == 0
|
||||||
if char_count and 0 <= startOffset < endOffset:
|
and AXText.get_substring(obj, startOffset, endOffset).strip()):
|
||||||
result = list(Atspi.Text.get_range_extents(
|
return result
|
||||||
obj, startOffset, endOffset, Atspi.CoordType.SCREEN))
|
|
||||||
except Exception as error:
|
tokens = ["WEB: Suspected bogus range extents for",
|
||||||
tokens = ["WEB: Exception getting range extents for", obj, ":", error]
|
obj, "(chars:", startOffset, ",", endOffset, "):", result]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
else:
|
|
||||||
if result[0] and result[1] and result[2] == 0 and result[3] == 0 \
|
|
||||||
and AXText.get_substring(obj, startOffset, endOffset).strip():
|
|
||||||
tokens = ["WEB: Suspected bogus range extents for",
|
|
||||||
obj, "(chars:", startOffset, ",", endOffset, "):", result]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
elif char_count:
|
|
||||||
return result
|
|
||||||
|
|
||||||
parent = AXObject.get_parent(obj)
|
parent = AXObject.get_parent(obj)
|
||||||
if (AXUtilities.is_menu(obj) or AXUtilities.is_list_item(obj)) \
|
if (AXUtilities.is_menu(obj) or AXUtilities.is_list_item(obj)) \
|
||||||
and (AXUtilities.is_combo_box(parent) or AXUtilities.is_list_box(parent)):
|
and (AXUtilities.is_combo_box(parent) or AXUtilities.is_list_box(parent)):
|
||||||
try:
|
extents = AXComponent.get_rect(parent)
|
||||||
ext = parent.queryComponent().getExtents(0)
|
|
||||||
except NotImplementedError:
|
|
||||||
tokens = ["WEB:", parent, "does not implement the component interface"]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
return [0, 0, 0, 0]
|
|
||||||
except Exception:
|
|
||||||
tokens = ["WEB: Exception getting extents for", parent]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
return [0, 0, 0, 0]
|
|
||||||
else:
|
else:
|
||||||
try:
|
extents = AXComponent.get_rect(obj)
|
||||||
ext = obj.queryComponent().getExtents(0)
|
|
||||||
except NotImplementedError:
|
|
||||||
tokens = ["WEB:", obj, "does not implement the component interface"]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
return [0, 0, 0, 0]
|
|
||||||
except Exception:
|
|
||||||
tokens = ["WEB: Exception getting extents for", obj]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
return [0, 0, 0, 0]
|
|
||||||
|
|
||||||
return [ext.x, ext.y, ext.width, ext.height]
|
return [extents.x, extents.y, extents.width, extents.height]
|
||||||
|
|
||||||
def descendantAtPoint(self, root, x, y, coordType=None):
|
def descendantAtPoint(self, root, x, y, coordType=None):
|
||||||
if coordType is None:
|
if coordType is None:
|
||||||
@@ -853,7 +839,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
if not self.isTextBlockElement(obj):
|
if not self.isTextBlockElement(obj):
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
child = self.findChildAtOffset(obj, offset)
|
child = AXHypertext.find_child_at_offset(obj, offset)
|
||||||
if child and not self.isTextBlockElement(child):
|
if child and not self.isTextBlockElement(child):
|
||||||
matches = [x for x in contents if x[0] == child]
|
matches = [x for x in contents if x[0] == child]
|
||||||
if len(matches) == 1:
|
if len(matches) == 1:
|
||||||
@@ -1351,7 +1337,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if char == self.EMBEDDED_OBJECT_CHARACTER:
|
if char == self.EMBEDDED_OBJECT_CHARACTER:
|
||||||
child = self.findChildAtOffset(obj, offset)
|
child = AXHypertext.find_child_at_offset(obj, offset)
|
||||||
if child:
|
if child:
|
||||||
return self._getContentsForObj(child, 0, boundary)
|
return self._getContentsForObj(child, 0, boundary)
|
||||||
|
|
||||||
@@ -1698,7 +1684,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
offset = max(0, offset)
|
offset = max(0, offset)
|
||||||
if (AXUtilities.is_tool_bar(obj) or AXUtilities.is_menu_bar(obj)) \
|
if (AXUtilities.is_tool_bar(obj) or AXUtilities.is_menu_bar(obj)) \
|
||||||
and not self._treatObjectAsWhole(obj):
|
and not self._treatObjectAsWhole(obj):
|
||||||
child = self.findChildAtOffset(obj, offset)
|
child = AXHypertext.find_child_at_offset(obj, offset)
|
||||||
if child:
|
if child:
|
||||||
obj = child
|
obj = child
|
||||||
offset = 0
|
offset = 0
|
||||||
@@ -1883,7 +1869,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
tokens = ["WEB: First context on line is: ", firstObj, ", ", firstOffset]
|
tokens = ["WEB: First context on line is: ", firstObj, ", ", firstOffset]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
|
|
||||||
skipSpace = not self.elementIsPreformattedText(firstObj)
|
skipSpace = not speech_and_verbosity_manager.getManager().get_speak_blank_lines()
|
||||||
obj, offset = self.previousContext(firstObj, firstOffset, skipSpace)
|
obj, offset = self.previousContext(firstObj, firstOffset, skipSpace)
|
||||||
if not obj and firstObj:
|
if not obj and firstObj:
|
||||||
tokens = ["WEB: Previous context is: ", obj, ", ", offset, ". Trying again."]
|
tokens = ["WEB: Previous context is: ", obj, ", ", offset, ". Trying again."]
|
||||||
@@ -1948,7 +1934,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
tokens = ["WEB: Last context on line is: ", lastObj, ", ", lastOffset]
|
tokens = ["WEB: Last context on line is: ", lastObj, ", ", lastOffset]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
|
|
||||||
skipSpace = not self.elementIsPreformattedText(lastObj)
|
skipSpace = not speech_and_verbosity_manager.getManager().get_speak_blank_lines()
|
||||||
obj, offset = self.nextContext(lastObj, lastOffset, skipSpace)
|
obj, offset = self.nextContext(lastObj, lastOffset, skipSpace)
|
||||||
if not obj and lastObj:
|
if not obj and lastObj:
|
||||||
tokens = ["WEB: Next context is: ", obj, ", ", offset, ". Trying again."]
|
tokens = ["WEB: Next context is: ", obj, ", ", offset, ". Trying again."]
|
||||||
@@ -2355,7 +2341,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
|
|
||||||
string, start, end = super().textAtPoint(obj, x, y, coordType, boundary)
|
string, start, end = super().textAtPoint(obj, x, y, coordType, boundary)
|
||||||
if string == self.EMBEDDED_OBJECT_CHARACTER:
|
if string == self.EMBEDDED_OBJECT_CHARACTER:
|
||||||
child = self.findChildAtOffset(obj, start)
|
child = AXHypertext.find_child_at_offset(obj, start)
|
||||||
if child:
|
if child:
|
||||||
return self.textAtPoint(child, x, y, coordType, boundary)
|
return self.textAtPoint(child, x, y, coordType, boundary)
|
||||||
|
|
||||||
@@ -4048,11 +4034,10 @@ class Utilities(script_utilities.Utilities):
|
|||||||
if uri and not uri.startswith('javascript'):
|
if uri and not uri.startswith('javascript'):
|
||||||
rv = False
|
rv = False
|
||||||
if rv and AXObject.supports_image(obj):
|
if rv and AXObject.supports_image(obj):
|
||||||
image = obj.queryImage()
|
if AXObject.get_image_description(obj):
|
||||||
if image.imageDescription:
|
|
||||||
rv = False
|
rv = False
|
||||||
elif not self.hasExplicitName(obj) and not self.isRedundantSVG(obj):
|
elif not self.hasExplicitName(obj) and not self.isRedundantSVG(obj):
|
||||||
width, height = image.getImageSize()
|
width, height = AXObject.get_image_size(obj)
|
||||||
if width > 25 and height > 25:
|
if width > 25 and height > 25:
|
||||||
rv = False
|
rv = False
|
||||||
if rv and AXObject.supports_text(obj):
|
if rv and AXObject.supports_text(obj):
|
||||||
@@ -4326,16 +4311,14 @@ class Utilities(script_utilities.Utilities):
|
|||||||
|
|
||||||
if event.type.startswith("object:text-changed") \
|
if event.type.startswith("object:text-changed") \
|
||||||
or event.type.startswith("object:text-selection-changed"):
|
or event.type.startswith("object:text-selection-changed"):
|
||||||
lastKey, mods = self.lastKeyAndModifiers()
|
if input_event_manager.get_manager().last_event_was_up_or_down():
|
||||||
if lastKey in ["Down", "Up"]:
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def treatEventAsSpinnerValueChange(self, event):
|
def treatEventAsSpinnerValueChange(self, event):
|
||||||
if event.type.startswith("object:text-caret-moved") and self.isSpinnerEntry(event.source):
|
if event.type.startswith("object:text-caret-moved") and self.isSpinnerEntry(event.source):
|
||||||
lastKey, mods = self.lastKeyAndModifiers()
|
if input_event_manager.get_manager().last_event_was_up_or_down():
|
||||||
if lastKey in ["Down", "Up"]:
|
|
||||||
obj, offset = self.getCaretContext()
|
obj, offset = self.getCaretContext()
|
||||||
return event.source == obj
|
return event.source == obj
|
||||||
|
|
||||||
@@ -4347,8 +4330,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
|
|
||||||
if event.type.startswith("object:text-") \
|
if event.type.startswith("object:text-") \
|
||||||
and self.isSingleLineAutocompleteEntry(event.source):
|
and self.isSingleLineAutocompleteEntry(event.source):
|
||||||
lastKey, mods = self.lastKeyAndModifiers()
|
return input_event_manager.get_manager().last_event_was_return()
|
||||||
return lastKey == "Return"
|
|
||||||
if event.type.startswith("object:text-") or event.type.endswith("accessible-name"):
|
if event.type.startswith("object:text-") or event.type.endswith("accessible-name"):
|
||||||
return AXUtilities.is_status_bar(event.source) or AXUtilities.is_label(event.source)
|
return AXUtilities.is_status_bar(event.source) or AXUtilities.is_label(event.source)
|
||||||
if event.type.startswith("object:children-changed"):
|
if event.type.startswith("object:children-changed"):
|
||||||
@@ -4377,8 +4359,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
if obj == event.source and isComboBoxItem(obj):
|
if obj == event.source and isComboBoxItem(obj):
|
||||||
lastKey, mods = self.lastKeyAndModifiers()
|
if input_event_manager.get_manager().last_event_was_up_or_down():
|
||||||
if lastKey in ["Down", "Up"]:
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@@ -4400,8 +4381,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
if AXUtilities.is_menu_related(event.source) \
|
if AXUtilities.is_menu_related(event.source) \
|
||||||
and AXUtilities.is_entry(cthulhu_state.locusOfFocus) \
|
and AXUtilities.is_entry(cthulhu_state.locusOfFocus) \
|
||||||
and AXUtilities.is_focused(cthulhu_state.locusOfFocus):
|
and AXUtilities.is_focused(cthulhu_state.locusOfFocus):
|
||||||
lastKey, mods = self.lastKeyAndModifiers()
|
if not input_event_manager.get_manager().last_event_was_up_or_down():
|
||||||
if lastKey not in ["Down", "Up"]:
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
@@ -4417,8 +4397,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
|
|
||||||
if AXUtilities.is_menu_item_of_any_kind(cthulhu_state.locusOfFocus) \
|
if AXUtilities.is_menu_item_of_any_kind(cthulhu_state.locusOfFocus) \
|
||||||
or AXUtilities.is_list_item(cthulhu_state.locusOfFocus):
|
or AXUtilities.is_list_item(cthulhu_state.locusOfFocus):
|
||||||
lastKey, mods = self.lastKeyAndModifiers()
|
return input_event_manager.get_manager().last_event_was_up_or_down()
|
||||||
return lastKey in ["Down", "Up"]
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -4785,12 +4764,6 @@ class Utilities(script_utilities.Utilities):
|
|||||||
container = obj
|
container = obj
|
||||||
contextObj, contextOffset = None, -1
|
contextObj, contextOffset = None, -1
|
||||||
while obj:
|
while obj:
|
||||||
if not AXObject.supports_text(obj):
|
|
||||||
tokens = ["WEB: Exception getting caret offset of", obj]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
obj = None
|
|
||||||
continue
|
|
||||||
|
|
||||||
offset = AXText.get_caret_offset(obj)
|
offset = AXText.get_caret_offset(obj)
|
||||||
if offset < 0:
|
if offset < 0:
|
||||||
tokens = ["WEB: Exception getting caret offset of", obj]
|
tokens = ["WEB: Exception getting caret offset of", obj]
|
||||||
@@ -4799,7 +4772,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
contextObj, contextOffset = obj, offset
|
contextObj, contextOffset = obj, offset
|
||||||
child = self.findChildAtOffset(obj, offset)
|
child = AXHypertext.find_child_at_offset(obj, offset)
|
||||||
if child:
|
if child:
|
||||||
obj = child
|
obj = child
|
||||||
else:
|
else:
|
||||||
@@ -4996,9 +4969,9 @@ class Utilities(script_utilities.Utilities):
|
|||||||
|
|
||||||
obj, offset = None, -1
|
obj, offset = None, -1
|
||||||
notify = True
|
notify = True
|
||||||
keyString, mods = self.lastKeyAndModifiers()
|
lastWasUp = input_event_manager.get_manager().last_event_was_up()
|
||||||
childCount = AXObject.get_child_count(event.source)
|
childCount = AXObject.get_child_count(event.source)
|
||||||
if keyString == "Up":
|
if lastWasUp:
|
||||||
if event.detail1 >= childCount:
|
if event.detail1 >= childCount:
|
||||||
msg = "WEB: Last child removed. Getting new location from end of parent."
|
msg = "WEB: Last child removed. Getting new location from end of parent."
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
@@ -5178,7 +5151,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
return obj, offset
|
return obj, offset
|
||||||
|
|
||||||
child = self.findChildAtOffset(obj, offset)
|
child = AXHypertext.find_child_at_offset(obj, offset)
|
||||||
if not child:
|
if not child:
|
||||||
msg = "WEB: Child at offset is null. Returning context unchanged."
|
msg = "WEB: Child at offset is null. Returning context unchanged."
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
@@ -5189,7 +5162,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
tokens = ["WEB: Child", child, "of", obj, "at offset", offset, "cannot be context."]
|
tokens = ["WEB: Child", child, "of", obj, "at offset", offset, "cannot be context."]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
offset += 1
|
offset += 1
|
||||||
child = self.findChildAtOffset(obj, offset)
|
child = AXHypertext.find_child_at_offset(obj, offset)
|
||||||
|
|
||||||
if self.isListItemMarker(child):
|
if self.isListItemMarker(child):
|
||||||
tokens = ["WEB: First caret context is next offset in", obj, ":",
|
tokens = ["WEB: First caret context is next offset in", obj, ":",
|
||||||
@@ -5234,7 +5207,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
if text:
|
if text:
|
||||||
allText = AXText.get_all_text(obj)
|
allText = AXText.get_all_text(obj)
|
||||||
for i in range(offset + 1, len(allText)):
|
for i in range(offset + 1, len(allText)):
|
||||||
child = self.findChildAtOffset(obj, i)
|
child = AXHypertext.find_child_at_offset(obj, i)
|
||||||
if child and allText[i] != self.EMBEDDED_OBJECT_CHARACTER:
|
if child and allText[i] != self.EMBEDDED_OBJECT_CHARACTER:
|
||||||
tokens = ["ERROR: Child", child, "found at offset with char '",
|
tokens = ["ERROR: Child", child, "found at offset with char '",
|
||||||
allText[i].replace("\n", "\\n"), "'"]
|
allText[i].replace("\n", "\\n"), "'"]
|
||||||
@@ -5310,7 +5283,7 @@ class Utilities(script_utilities.Utilities):
|
|||||||
if offset == -1 or offset > len(allText):
|
if offset == -1 or offset > len(allText):
|
||||||
offset = len(allText)
|
offset = len(allText)
|
||||||
for i in range(offset - 1, -1, -1):
|
for i in range(offset - 1, -1, -1):
|
||||||
child = self.findChildAtOffset(obj, i)
|
child = AXHypertext.find_child_at_offset(obj, i)
|
||||||
if child and allText[i] != self.EMBEDDED_OBJECT_CHARACTER:
|
if child and allText[i] != self.EMBEDDED_OBJECT_CHARACTER:
|
||||||
tokens = ["ERROR: Child", child, "found at offset with char '",
|
tokens = ["ERROR: Child", child, "found at offset with char '",
|
||||||
allText[i].replace("\n", "\\n"), "'"]
|
allText[i].replace("\n", "\\n"), "'"]
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ from gi.repository import Atspi
|
|||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
from cthulhu import debug
|
from cthulhu import debug
|
||||||
|
from cthulhu import input_event_manager
|
||||||
from cthulhu import messages
|
from cthulhu import messages
|
||||||
from cthulhu import object_properties
|
from cthulhu import object_properties
|
||||||
from cthulhu import cthulhu_state
|
from cthulhu import cthulhu_state
|
||||||
@@ -302,12 +303,13 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
|
|||||||
if args.get('leaving'):
|
if args.get('leaving'):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
lastKey, mods = self._script.utilities.lastKeyAndModifiers()
|
manager = input_event_manager.get_manager()
|
||||||
if (lastKey in ['Down', 'Right'] or self._script.inSayAll()) and args.get('startOffset'):
|
if (manager.last_event_was_forward_caret_navigation() or self._script.inSayAll()) \
|
||||||
|
and args.get('startOffset'):
|
||||||
return []
|
return []
|
||||||
if lastKey in ['Up', 'Left']:
|
if manager.last_event_was_backward_caret_navigation():
|
||||||
text = self._script.utilities.queryNonEmptyText(obj)
|
if self._script.utilities.treatAsTextObject(obj) \
|
||||||
if text and args.get('endOffset') not in [None, AXText.get_character_count(obj)]:
|
and args.get('endOffset') not in [None, AXText.get_character_count(obj)]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
@@ -358,8 +360,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
|
|||||||
|
|
||||||
if self._script.utilities.isContentEditableWithEmbeddedObjects(obj) \
|
if self._script.utilities.isContentEditableWithEmbeddedObjects(obj) \
|
||||||
or self._script.utilities.isDocument(obj):
|
or self._script.utilities.isDocument(obj):
|
||||||
lastKey, mods = self._script.utilities.lastKeyAndModifiers()
|
if input_event_manager.get_manager().last_event_was_caret_navigation():
|
||||||
if lastKey in ["Home", "End", "Up", "Down", "Left", "Right", "Page_Up", "Page_Down"]:
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if AXUtilities.is_page_tab(priorObj) and AXObject.get_name(priorObj) == objName:
|
if AXUtilities.is_page_tab(priorObj) and AXObject.get_name(priorObj) == objName:
|
||||||
@@ -594,13 +595,14 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
|
|||||||
if self._script.utilities.isMenuInCollapsedSelectElement(obj):
|
if self._script.utilities.isMenuInCollapsedSelectElement(obj):
|
||||||
doNotSpeak.append(Atspi.Role.MENU)
|
doNotSpeak.append(Atspi.Role.MENU)
|
||||||
|
|
||||||
lastKey, mods = self._script.utilities.lastKeyAndModifiers()
|
|
||||||
isEditable = AXUtilities.is_editable(obj)
|
isEditable = AXUtilities.is_editable(obj)
|
||||||
|
|
||||||
if isEditable and not self._script.utilities.isContentEditableWithEmbeddedObjects(obj):
|
if isEditable and not self._script.utilities.isContentEditableWithEmbeddedObjects(obj):
|
||||||
if ((lastKey in ["Down", "Right"] and not mods) or self._script.inSayAll()) and start:
|
manager = input_event_manager.get_manager()
|
||||||
|
if (manager.last_event_was_forward_caret_navigation() or self._script.inSayAll()) \
|
||||||
|
and start:
|
||||||
return []
|
return []
|
||||||
if lastKey in ["Up", "Left"] and not mods:
|
if manager.last_event_was_backward_caret_navigation():
|
||||||
text = self._script.utilities.queryNonEmptyText(obj)
|
text = self._script.utilities.queryNonEmptyText(obj)
|
||||||
if text and end not in [None, AXText.get_character_count(obj)]:
|
if text and end not in [None, AXText.get_character_count(obj)]:
|
||||||
return []
|
return []
|
||||||
@@ -611,8 +613,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
|
|||||||
elif isEditable and self._script.utilities.isDocument(obj):
|
elif isEditable and self._script.utilities.isDocument(obj):
|
||||||
parent = AXObject.get_parent(obj)
|
parent = AXObject.get_parent(obj)
|
||||||
if parent and not AXUtilities.is_editable(parent) \
|
if parent and not AXUtilities.is_editable(parent) \
|
||||||
and lastKey not in \
|
and not input_event_manager.get_manager().last_event_was_caret_navigation():
|
||||||
["Home", "End", "Up", "Down", "Left", "Right", "Page_Up", "Page_Down"]:
|
|
||||||
result.append(object_properties.ROLE_EDITABLE_CONTENT)
|
result.append(object_properties.ROLE_EDITABLE_CONTENT)
|
||||||
result.extend(self.voice(speech_generator.SYSTEM, obj=obj, **args))
|
result.extend(self.voice(speech_generator.SYSTEM, obj=obj, **args))
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ from . import settings_manager
|
|||||||
from . import speech
|
from . import speech
|
||||||
from . import text_attribute_names
|
from . import text_attribute_names
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
|
from .ax_table import AXTable
|
||||||
from .ax_text import AXText
|
from .ax_text import AXText
|
||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
from .ax_utilities_relation import AXUtilitiesRelation
|
from .ax_utilities_relation import AXUtilitiesRelation
|
||||||
@@ -931,11 +932,7 @@ class SpeechGenerator(generator.Generator):
|
|||||||
it exists. Otherwise, an empty array is returned.
|
it exists. Otherwise, an empty array is returned.
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
try:
|
if AXObject.supports_image(obj):
|
||||||
obj.queryImage()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
args['role'] = Atspi.Role.IMAGE
|
args['role'] = Atspi.Role.IMAGE
|
||||||
result.extend(self.generate(obj, **args))
|
result.extend(self.generate(obj, **args))
|
||||||
result.extend(self.voice(DEFAULT, obj=obj, **args))
|
result.extend(self.voice(DEFAULT, obj=obj, **args))
|
||||||
@@ -1143,15 +1140,9 @@ class SpeechGenerator(generator.Generator):
|
|||||||
parent = AXObject.get_parent(obj)
|
parent = AXObject.get_parent(obj)
|
||||||
if AXUtilities.is_table_cell(parent):
|
if AXUtilities.is_table_cell(parent):
|
||||||
obj = parent
|
obj = parent
|
||||||
parent = self._script.utilities.getTable(obj)
|
row, col = self._script.utilities.coordinatesForCell(obj, False)
|
||||||
try:
|
if col < 0 and args.get('guessCoordinates', False):
|
||||||
table = parent.queryTable()
|
col = self._script.pointOfReference.get('lastColumn', -1)
|
||||||
except Exception:
|
|
||||||
if args.get('guessCoordinates', False):
|
|
||||||
col = self._script.pointOfReference.get('lastColumn', -1)
|
|
||||||
else:
|
|
||||||
index = self._script.utilities.cellIndex(obj)
|
|
||||||
col = table.getColumnAtIndex(index)
|
|
||||||
if col >= 0:
|
if col >= 0:
|
||||||
result.append(messages.TABLE_COLUMN % (col + 1))
|
result.append(messages.TABLE_COLUMN % (col + 1))
|
||||||
if result:
|
if result:
|
||||||
@@ -1182,15 +1173,9 @@ class SpeechGenerator(generator.Generator):
|
|||||||
parent = AXObject.get_parent(obj)
|
parent = AXObject.get_parent(obj)
|
||||||
if AXUtilities.is_table_cell(parent):
|
if AXUtilities.is_table_cell(parent):
|
||||||
obj = parent
|
obj = parent
|
||||||
parent = self._script.utilities.getTable(obj)
|
row, col = self._script.utilities.coordinatesForCell(obj, False)
|
||||||
try:
|
if row < 0 and args.get('guessCoordinates', False):
|
||||||
table = parent.queryTable()
|
row = self._script.pointOfReference.get('lastRow', -1)
|
||||||
except Exception:
|
|
||||||
if args.get('guessCoordinates', False):
|
|
||||||
row = self._script.pointOfReference.get('lastRow', -1)
|
|
||||||
else:
|
|
||||||
index = self._script.utilities.cellIndex(obj)
|
|
||||||
row = table.getRowAtIndex(index)
|
|
||||||
if row >= 0:
|
if row >= 0:
|
||||||
result.append(messages.TABLE_ROW % (row + 1))
|
result.append(messages.TABLE_ROW % (row + 1))
|
||||||
if result:
|
if result:
|
||||||
@@ -1210,21 +1195,17 @@ class SpeechGenerator(generator.Generator):
|
|||||||
parent = AXObject.get_parent(obj)
|
parent = AXObject.get_parent(obj)
|
||||||
if AXUtilities.is_table_cell(parent):
|
if AXUtilities.is_table_cell(parent):
|
||||||
obj = parent
|
obj = parent
|
||||||
parent = self._script.utilities.getTable(obj)
|
table = self._script.utilities.getTable(obj)
|
||||||
try:
|
if table:
|
||||||
table = parent.queryTable()
|
row, col = self._script.utilities.coordinatesForCell(obj, False)
|
||||||
except Exception:
|
rows, cols = self._script.utilities.rowAndColumnCount(table, False)
|
||||||
table = None
|
if row >= 0 and col >= 0 and rows > 0 and cols > 0:
|
||||||
else:
|
result.append(messages.TABLE_COLUMN_DETAILED \
|
||||||
index = self._script.utilities.cellIndex(obj)
|
% {"index" : (col + 1),
|
||||||
col = table.getColumnAtIndex(index)
|
"total" : cols})
|
||||||
row = table.getRowAtIndex(index)
|
result.append(messages.TABLE_ROW_DETAILED \
|
||||||
result.append(messages.TABLE_COLUMN_DETAILED \
|
% {"index" : (row + 1),
|
||||||
% {"index" : (col + 1),
|
"total" : rows})
|
||||||
"total" : table.nColumns})
|
|
||||||
result.append(messages.TABLE_ROW_DETAILED \
|
|
||||||
% {"index" : (row + 1),
|
|
||||||
"total" : table.nRows})
|
|
||||||
if result:
|
if result:
|
||||||
result.extend(self.voice(SYSTEM, obj=obj, **args))
|
result.extend(self.voice(SYSTEM, obj=obj, **args))
|
||||||
return result
|
return result
|
||||||
@@ -2817,16 +2798,15 @@ class SpeechGenerator(generator.Generator):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _generateMathTableStart(self, obj, **args):
|
def _generateMathTableStart(self, obj, **args):
|
||||||
try:
|
|
||||||
table = obj.queryTable()
|
|
||||||
except Exception:
|
|
||||||
return []
|
|
||||||
|
|
||||||
nestingLevel = self._script.utilities.getMathNestingLevel(obj)
|
nestingLevel = self._script.utilities.getMathNestingLevel(obj)
|
||||||
|
rows = AXTable.get_row_count(obj, prefer_attribute=False)
|
||||||
|
cols = AXTable.get_column_count(obj, prefer_attribute=False)
|
||||||
|
if rows < 0 or cols < 0:
|
||||||
|
return []
|
||||||
if nestingLevel > 0:
|
if nestingLevel > 0:
|
||||||
result = [messages.mathNestedTableSize(table.nRows, table.nColumns)]
|
result = [messages.mathNestedTableSize(rows, cols)]
|
||||||
else:
|
else:
|
||||||
result = [messages.mathTableSize(table.nRows, table.nColumns)]
|
result = [messages.mathTableSize(rows, cols)]
|
||||||
result.extend(self.voice(SYSTEM, obj=obj, **args))
|
result.extend(self.voice(SYSTEM, obj=obj, **args))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ from . import settings_manager
|
|||||||
from .ax_collection import AXCollection
|
from .ax_collection import AXCollection
|
||||||
from .ax_event_synthesizer import AXEventSynthesizer
|
from .ax_event_synthesizer import AXEventSynthesizer
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
|
from .ax_table import AXTable
|
||||||
from .ax_text import AXText
|
from .ax_text import AXText
|
||||||
from .ax_selection import AXSelection
|
from .ax_selection import AXSelection
|
||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
@@ -975,11 +976,11 @@ class StructuralNavigation:
|
|||||||
- obj: the accessible table whose caption we want.
|
- obj: the accessible table whose caption we want.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
caption = obj.queryTable().caption
|
caption = AXTable.get_caption(obj)
|
||||||
if not AXObject.supports_text(caption):
|
if not caption:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self._script.utilities.displayedText(caption)
|
return AXText.get_all_text(caption)
|
||||||
|
|
||||||
def _getTableDescription(self, obj):
|
def _getTableDescription(self, obj):
|
||||||
"""Returns a string which describes the table."""
|
"""Returns a string which describes the table."""
|
||||||
@@ -1246,12 +1247,7 @@ class StructuralNavigation:
|
|||||||
if item:
|
if item:
|
||||||
text = AXObject.get_name(item)
|
text = AXObject.get_name(item)
|
||||||
if not text and AXUtilities.is_image(obj):
|
if not text and AXUtilities.is_image(obj):
|
||||||
try:
|
text = AXObject.get_image_description(obj) or AXObject.get_description(obj)
|
||||||
image = obj.queryImage()
|
|
||||||
except Exception:
|
|
||||||
text = AXObject.get_description(obj)
|
|
||||||
else:
|
|
||||||
text = image.imageDescription or AXObject.get_description(obj)
|
|
||||||
if not text:
|
if not text:
|
||||||
parent = AXObject.get_parent(obj)
|
parent = AXObject.get_parent(obj)
|
||||||
if AXUtilities.is_link(parent):
|
if AXUtilities.is_link(parent):
|
||||||
@@ -2115,11 +2111,11 @@ class StructuralNavigation:
|
|||||||
if attrs.get('layout-guess') == 'true':
|
if attrs.get('layout-guess') == 'true':
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
if not AXObject.supports_table(obj):
|
||||||
return obj.queryTable().nRows > 0
|
|
||||||
except Exception:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
return AXTable.get_row_count(obj, prefer_attribute=False) > 0
|
||||||
|
|
||||||
return AXUtilities.find_all_tables(document, is_not_layout_or_empty)
|
return AXUtilities.find_all_tables(document, is_not_layout_or_empty)
|
||||||
|
|
||||||
def _tablePresentation(self, obj, arg=None):
|
def _tablePresentation(self, obj, arg=None):
|
||||||
@@ -2128,7 +2124,7 @@ class StructuralNavigation:
|
|||||||
if caption:
|
if caption:
|
||||||
self._script.presentMessage(caption)
|
self._script.presentMessage(caption)
|
||||||
self._script.presentMessage(self._getTableDescription(obj))
|
self._script.presentMessage(self._getTableDescription(obj))
|
||||||
cell = obj.queryTable().getAccessibleAt(0, 0)
|
cell = AXTable.get_cell_at(obj, 0, 0)
|
||||||
if not cell:
|
if not cell:
|
||||||
tokens = ["STRUCTURAL NAVIGATION: Broken table interface for", obj]
|
tokens = ["STRUCTURAL NAVIGATION: Broken table interface for", obj]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ from . import cthulhu_state
|
|||||||
from . import settings
|
from . import settings
|
||||||
|
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
|
from .ax_table import AXTable
|
||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
from .cthulhu_i18n import _ # for gettext support
|
from .cthulhu_i18n import _ # for gettext support
|
||||||
|
|
||||||
@@ -613,10 +614,7 @@ class TutorialGenerator:
|
|||||||
|
|
||||||
if (not alreadyFocused):
|
if (not alreadyFocused):
|
||||||
parent = AXObject.get_parent(obj)
|
parent = AXObject.get_parent(obj)
|
||||||
try:
|
parent_table = AXTable.get_table(parent)
|
||||||
parent_table = parent.queryTable()
|
|
||||||
except Exception:
|
|
||||||
parent_table = None
|
|
||||||
readFullRow = self._script.utilities.shouldReadFullRow(obj)
|
readFullRow = self._script.utilities.shouldReadFullRow(obj)
|
||||||
if readFullRow and parent_table and not self._script.utilities.isLayoutOnly(parent):
|
if readFullRow and parent_table and not self._script.utilities.isLayoutOnly(parent):
|
||||||
utterances.extend(self._getTutorialForTableCell(obj,
|
utterances.extend(self._getTutorialForTableCell(obj,
|
||||||
|
|||||||
Reference in New Issue
Block a user