[orca] Begin work on label inference (aka "label guess") functionality for WebKitGtk content
- From: Joanmarie Diggs <joanied src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [orca] Begin work on label inference (aka "label guess") functionality for WebKitGtk content
- Date: Mon, 5 Dec 2011 20:47:20 +0000 (UTC)
commit ec2cbd70501101e6ceaa8fde1646ee55b9ec7b31
Author: Joanmarie Diggs <jdiggs igalia com>
Date: Sun Dec 4 20:56:39 2011 +0100
Begin work on label inference (aka "label guess") functionality for WebKitGtk content
src/orca/Makefile.am | 1 +
src/orca/label_inference.py | 198 ++++++++++++++++++++
src/orca/script.py | 6 +
src/orca/script_utilities.py | 30 +++-
.../scripts/toolkits/WebKitGtk/script_utilities.py | 12 --
.../scripts/toolkits/WebKitGtk/speech_generator.py | 29 +++-
6 files changed, 261 insertions(+), 15 deletions(-)
---
diff --git a/src/orca/Makefile.am b/src/orca/Makefile.am
index 30288eb..fab8a97 100644
--- a/src/orca/Makefile.am
+++ b/src/orca/Makefile.am
@@ -34,6 +34,7 @@ orca_python_PYTHON = \
input_event.py \
keybindings.py \
keynames.py \
+ label_inference.py \
laptop_keyboardmap.py \
liveregions.py \
mouse_review.py \
diff --git a/src/orca/label_inference.py b/src/orca/label_inference.py
new file mode 100644
index 0000000..7cd0c13
--- /dev/null
+++ b/src/orca/label_inference.py
@@ -0,0 +1,198 @@
+# Orca
+#
+# Copyright (C) 2011 Igalia, S.L.
+#
+# Author: Joanmarie Diggs <jdiggs igalia com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Heuristic means to infer the functional/displayed label of a widget."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (C) 2011 Igalia, S.L."
+__license__ = "LGPL"
+
+import pyatspi
+
+class LabelInference:
+
+ def __init__(self, script):
+ """Creates an instance of the LabelInference class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ self._script = script
+
+ def infer(self, obj, focusedOnly=True):
+ """Attempt to infer the functional/displayed label of obj.
+
+ Arguments
+ - obj: the unlabeled widget
+ - focusedOnly: If True, only infer if the widget has focus.
+
+ Returns the text which we think is the label, or None.
+ """
+
+ isFocused = obj.getState().contains(pyatspi.STATE_FOCUSED)
+ if focusedOnly and not isFocused:
+ return None
+
+ result = None
+ if not result:
+ result = self.inferFromLine(obj)
+ if not result:
+ result = self.inferFromTable(obj)
+ if not result:
+ result = self.inferFromOtherLines(obj)
+ if not result:
+ result = obj.name
+ if not result:
+ result = obj.description
+ if result:
+ result = result.strip()
+
+ return result
+
+ def _isWidget(self, obj):
+ """Returns True if the given object is a widget."""
+
+ widgetRoles = [pyatspi.ROLE_CHECK_BOX,
+ pyatspi.ROLE_RADIO_BUTTON,
+ pyatspi.ROLE_COMBO_BOX,
+ pyatspi.ROLE_DOCUMENT_FRAME,
+ pyatspi.ROLE_LIST,
+ pyatspi.ROLE_ENTRY,
+ pyatspi.ROLE_PASSWORD_TEXT,
+ pyatspi.ROLE_PUSH_BUTTON]
+
+ return obj.getRole() in widgetRoles
+
+ def _getExtents(self, obj, startOffset, endOffset):
+ """Returns (x, y, width, height) of the text at the given offsets
+ if the object implements accessible text, or just the extents of
+ the object if it doesn't implement accessible text."""
+
+ extents = 0, 0, 0, 0
+
+ try:
+ text = obj.queryText()
+ except AttributeError:
+ return extents
+ except NotImplementedError:
+ pass
+ else:
+ skipTextExtents = [pyatspi.ROLE_ENTRY, pyatspi.ROLE_PASSWORD_TEXT]
+ if not obj.getRole() in skipTextExtents:
+ extents = text.getRangeExtents(startOffset, endOffset, 0)
+
+ if extents[2] and extents[3]:
+ return extents
+
+ ext = obj.queryComponent().getExtents(0)
+ extents = ext.x, ext.y, ext.width, ext.height
+
+ return extents
+
+ def _getLineContents(self, obj):
+ """Get the (obj, startOffset, endOffset, string) tuples for the line
+ containing the object, obj."""
+
+ boundary = pyatspi.TEXT_BOUNDARY_LINE_START
+ contents = self._script.utilities.getObjectsFromEOCs(obj, boundary)
+ content = filter(lambda o: o[0] == obj, contents)
+ if content == contents:
+ start, end = self._script.utilities.getHyperlinkRange(obj)
+ contents = self._script.utilities.getObjectsFromEOCs(
+ obj.parent, boundary, start)
+
+ return contents
+
+ def inferFromLine(self, obj, proximity=75):
+ """Attempt to infer the functional/displayed label of obj by
+ looking at the contents of the current line.
+
+ Arguments
+ - obj: the unlabeled widget
+ - proximity: pixels expected for a match
+
+ Returns the text which we think is the label, or None.
+ """
+
+ contents = self._getLineContents(obj)
+ content = filter(lambda o: o[0] == obj, contents)
+ try:
+ index = contents.index(content[0])
+ except IndexError:
+ index = len(contents)
+
+ onLeft = contents[0:index]
+ onLeft.reverse()
+ try:
+ onRight = contents[index+1:]
+ except IndexError:
+ onRight = [()]
+
+ # Normally widgets do not label other widgets.
+ onLeft = filter(lambda o: not self._isWidget(o[0]), onLeft)
+ onRight = filter(lambda o: not self._isWidget(o[0]), onRight)
+
+ # Proximity matters, both in terms of objects and of pixels.
+ if onLeft:
+ onLeft = onLeft[0]
+ if onRight:
+ onRight = onRight[0]
+ extents = self._getExtents(obj, 0, 1)
+
+ # Sometimes we should prefer what's to the right of the widget.
+ onRightRoles = [pyatspi.ROLE_CHECK_BOX, pyatspi.ROLE_RADIO_BUTTON]
+ preferRight = obj.getRole() in onRightRoles
+
+ lObj = rObj = None
+ lString = rString = ''
+ lDistance = rDistance = -1
+
+ if onLeft:
+ lObj, lStart, lEnd, lString = onLeft
+ lString = (lString or lObj.name).strip()
+ if lString:
+ lExtents = self._getExtents(lObj, lStart, lEnd)
+ lDistance = extents[0] - (lExtents[0] + lExtents[2])
+ if not preferRight and lDistance <= proximity:
+ return lString
+
+ if onRight:
+ rObj, rStart, rEnd, rString = onRight
+ rString = (rString or rObj.name).strip()
+ if rString:
+ rExtents = self._getExtents(rObj, rStart, rEnd)
+ rDistance = rExtents[0] - (extents[0] + extents[2])
+ if preferRight and rDistance <= proximity:
+ return rString
+
+ if rString and rDistance <= proximity:
+ return rString
+
+ return None
+
+ def inferFromTable(self, obj):
+ pass
+
+ def inferFromOtherLines(self, obj):
+ pass
diff --git a/src/orca/script.py b/src/orca/script.py
index e8a86a7..61adfab 100644
--- a/src/orca/script.py
+++ b/src/orca/script.py
@@ -45,6 +45,7 @@ import braille_generator
import debug
import flat_review
import formatting
+import label_inference
import keybindings
import orca_state
import script_utilities
@@ -83,6 +84,7 @@ class Script:
self.presentIfInactive = True
self.utilities = self.getUtilities()
+ self.labelInference = self.getLabelInference()
self.structuralNavigation = self.getStructuralNavigation()
self.chat = self.getChat()
self.inputEventHandlers = {}
@@ -211,6 +213,10 @@ class Script:
"""
return script_utilities.Utilities(self)
+ def getLabelInference(self):
+ """Returns the label inference functionality for this script."""
+ return label_inference.LabelInference(self)
+
def getEnabledStructuralNavigationTypes(self):
"""Returns a list of the structural navigation object types
enabled in this script.
diff --git a/src/orca/script_utilities.py b/src/orca/script_utilities.py
index 90672e4..dea3136 100644
--- a/src/orca/script_utilities.py
+++ b/src/orca/script_utilities.py
@@ -2452,7 +2452,7 @@ class Utilities:
try:
unchanged = newSegment == segment
- except UnicodeEncodeError, UnicodeDecodeError:
+ except (UnicodeEncodeError, UnicodeDecodeError):
unchanged = True
if unchanged:
@@ -2965,3 +2965,31 @@ class Utilities:
except:
debug.printException(debug.LEVEL_WARNING)
return ""
+
+ def getObjectsFromEOCs(self, obj, boundary=None, offset=None):
+ """Breaks the string containing a mixture of text and embedded object
+ characters into a list of (obj, startOffset, endOffset, string) tuples.
+
+ Arguments
+ - obj: the object whose EOCs we need to expand into tuples
+ - boundary: the pyatspi text boundary type. If None, get all text.
+ - offset: the character offset. If None, use the current offset.
+
+ Returns a list of (obj, startOffset, endOffset, string) tuples.
+ """
+
+ # For now, each script should implement this functionality itself.
+
+ return []
+
+ @staticmethod
+ def getHyperlinkRange(obj):
+ """Returns the start and end indices associated with the embedded
+ object, obj."""
+
+ try:
+ hyperlink = obj.queryHyperlink()
+ except NotImplementedError:
+ return 0, 0
+
+ return hyperlink.startIndex, hyperlink.endIndex
diff --git a/src/orca/scripts/toolkits/WebKitGtk/script_utilities.py b/src/orca/scripts/toolkits/WebKitGtk/script_utilities.py
index a25109e..4a1624d 100644
--- a/src/orca/scripts/toolkits/WebKitGtk/script_utilities.py
+++ b/src/orca/scripts/toolkits/WebKitGtk/script_utilities.py
@@ -112,18 +112,6 @@ class Utilities(script_utilities.Utilities):
return text
- @staticmethod
- def getHyperlinkRange(obj):
- """Returns the start and end indices associated with the embedded
- object, obj."""
-
- try:
- hyperlink = obj.queryHyperlink()
- except NotImplementedError:
- return 0, 0
-
- return hyperlink.startIndex, hyperlink.endIndex
-
def getObjectsFromEOCs(self, obj, boundary=None, offset=None):
"""Breaks the string containing a mixture of text and embedded object
characters into a list of (obj, startOffset, endOffset, string) tuples.
diff --git a/src/orca/scripts/toolkits/WebKitGtk/speech_generator.py b/src/orca/scripts/toolkits/WebKitGtk/speech_generator.py
index 5be3def..923c8fe 100644
--- a/src/orca/scripts/toolkits/WebKitGtk/speech_generator.py
+++ b/src/orca/scripts/toolkits/WebKitGtk/speech_generator.py
@@ -1,8 +1,9 @@
# Orca
#
# Copyright (C) 2010 Joanmarie Diggs
+# Copyright (C) 2011 Igalia, S.L.
#
-# Author: Joanmarie Diggs <joanied gnome org>
+# Author: Joanmarie Diggs <jdiggs igalia com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -22,7 +23,8 @@
__id__ = "$Id$"
__version__ = "$Revision$"
__date__ = "$Date$"
-__copyright__ = "Copyright (c) 2010 Joanmarie Diggs"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs" \
+ "Copyright (c) 2011 Igalia, S.L."
__license__ = "LGPL"
import pyatspi
@@ -56,6 +58,29 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
return voice
+ def _generateLabel(self, obj, **args):
+ result = \
+ speech_generator.SpeechGenerator._generateLabel(self, obj, **args)
+ if result:
+ return result
+
+ role = args.get('role', obj.getRole())
+ inferRoles = [pyatspi.ROLE_CHECK_BOX,
+ pyatspi.ROLE_COMBO_BOX,
+ pyatspi.ROLE_ENTRY,
+ pyatspi.ROLE_LIST,
+ pyatspi.ROLE_PASSWORD_TEXT,
+ pyatspi.ROLE_RADIO_BUTTON]
+ if not role in inferRoles:
+ return result
+
+ label = self._script.labelInference.infer(obj)
+ if label:
+ result.append(label)
+ result.extend(self.voice(speech_generator.DEFAULT))
+
+ return result
+
def _generateRoleName(self, obj, **args):
if _settingsManager.getSetting('onlySpeakDisplayedText'):
return []
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]