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