[orca] Fix for bug #645465 Say All needs to be implemented for WebKitGtk-based apps



commit 6af52fb7186ce51e70d6b720108fc5a276eabdcd
Author: Joanmarie Diggs <joanmarie diggs gmail com>
Date:   Mon Mar 21 11:52:52 2011 -0400

    Fix for bug #645465 Say All needs to be implemented for WebKitGtk-based apps

 src/orca/scripts/toolkits/WebKitGtk/script.py      |  117 ++++++++++++++++++++
 .../scripts/toolkits/WebKitGtk/script_utilities.py |   51 +++++++++
 .../scripts/toolkits/WebKitGtk/speech_generator.py |    9 ++
 3 files changed, 177 insertions(+), 0 deletions(-)
---
diff --git a/src/orca/scripts/toolkits/WebKitGtk/script.py b/src/orca/scripts/toolkits/WebKitGtk/script.py
index 2742a78..335f172 100644
--- a/src/orca/scripts/toolkits/WebKitGtk/script.py
+++ b/src/orca/scripts/toolkits/WebKitGtk/script.py
@@ -29,7 +29,10 @@ import pyatspi
 import pyatspi.utils as utils
 
 import orca.scripts.default as default
+import orca.input_event as input_event
 import orca.orca as orca
+import orca.settings as settings
+import orca.speechserver as speechserver
 import orca.orca_state as orca_state
 import orca.speech as speech
 from orca.orca_i18n import _
@@ -39,6 +42,8 @@ from braille_generator import BrailleGenerator
 from speech_generator import SpeechGenerator
 from script_utilities import Utilities
 
+_settingsManager = getattr(orca, '_settingsManager')
+
 ########################################################################
 #                                                                      #
 # The WebKitGtk script class.                                          #
@@ -84,6 +89,19 @@ class Script(default.Script):
         self.inputEventHandlers.update(
             self.structuralNavigation.inputEventHandlers)
 
+        self.inputEventHandlers["sayAllHandler"] = \
+            input_event.InputEventHandler(
+                Script.sayAll,
+                # Translators: the Orca "SayAll" command allows the
+                # user to press a key and have the entire document in
+                # a window be automatically spoken to the user.  If
+                # the user presses any key during a SayAll operation,
+                # the speech will be interrupted and the cursor will
+                # be positioned at the point where the speech was
+                # interrupted.
+                #
+                _("Speaks entire document."))
+
     def getKeyBindings(self):
         """Defines the key bindings for this script. Setup the default
         key bindings, then add one in for reading the input line.
@@ -343,3 +361,102 @@ class Script(default.Script):
                 break
 
         return child, index
+
+    def sayAll(self, inputEvent):
+        """Speaks the contents of the document beginning with the present
+        location.  Overridden in this script because the sayAll could have
+        been started on an object without text (such as an image).
+        """
+
+        if not self.utilities.isWebKitGtk(orca_state.locusOfFocus):
+            return default.Script.sayAll(self, inputEvent)
+
+        speech.sayAll(self.textLines(orca_state.locusOfFocus),
+                      self.__sayAllProgressCallback)
+
+        return True
+
+    def getTextSegments(self, obj, boundary, offset=0):
+        segments = []
+        text = obj.queryText()
+        length = text.characterCount
+        string, start, end = text.getTextAtOffset(offset, boundary)
+        while string and offset < length:
+            string = self.utilities.adjustForRepeats(string)
+            voice = self.speechGenerator.getVoiceForString(obj, string)
+            string = self.utilities.adjustForLinks(obj, string, start)
+            segments.append([string, start, end, voice])
+            offset = end
+            string, start, end = text.getTextAtOffset(offset, boundary)
+
+        return segments
+
+    def textLines(self, obj):
+        """Creates a generator that can be used to iterate over each line
+        of a text object, starting at the caret offset.
+
+        Arguments:
+        - obj: an Accessible that has a text specialization
+
+        Returns an iterator that produces elements of the form:
+        [SayAllContext, acss], where SayAllContext has the text to be
+        spoken and acss is an ACSS instance for speaking the text.
+        """
+
+        document = utils.findAncestor(
+            obj, lambda x: x.getRole() == pyatspi.ROLE_DOCUMENT_FRAME)
+        allTextObjs = utils.findAllDescendants(
+            document, lambda x: 'Text' in utils.listInterfaces(x))
+        allTextObjs = allTextObjs[allTextObjs.index(obj):len(allTextObjs)]
+        textObjs = filter(lambda x: x.parent not in allTextObjs, allTextObjs)
+        if not textObjs:
+            return
+
+        boundary = pyatspi.TEXT_BOUNDARY_LINE_START
+        sayAllStyle = _settingsManager.getSetting('sayAllStyle')
+        if sayAllStyle == settings.SAYALL_STYLE_SENTENCE:
+            boundary = pyatspi.TEXT_BOUNDARY_SENTENCE_START
+
+        offset = textObjs[0].queryText().caretOffset
+        for textObj in textObjs:
+            textSegments = self.getTextSegments(textObj, boundary, offset)
+            roleInfo = self.speechGenerator.getRoleName(textObj)
+            if roleInfo:
+                roleName, voice = roleInfo
+                textSegments.append([roleName, 0, -1, voice])
+
+            for (string, start, end, voice) in textSegments:
+                yield [speechserver.SayAllContext(textObj, string, start, end),
+                       voice]
+
+            offset = 0
+
+    def __sayAllProgressCallback(self, context, progressType):
+        if progressType == speechserver.SayAllContext.PROGRESS:
+            return
+
+        obj = context.obj
+        orca.setLocusOfFocus(None, obj, notifyScript=False)
+
+        offset = context.currentOffset
+        text = obj.queryText()
+
+        if progressType == speechserver.SayAllContext.INTERRUPTED:
+            text.setCaretOffset(offset)
+            return
+
+        # SayAllContext.COMPLETED doesn't necessarily mean done with SayAll;
+        # 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 filter(lambda l: l.startIndex <= offset <= l.endIndex, links):
+                return
+
+        text.setCaretOffset(offset)
diff --git a/src/orca/scripts/toolkits/WebKitGtk/script_utilities.py b/src/orca/scripts/toolkits/WebKitGtk/script_utilities.py
index 97e3cd9..879291b 100644
--- a/src/orca/scripts/toolkits/WebKitGtk/script_utilities.py
+++ b/src/orca/scripts/toolkits/WebKitGtk/script_utilities.py
@@ -84,3 +84,54 @@ class Utilities(script_utilities.Utilities):
                    and not state.contains(pyatspi.STATE_EDITABLE)
 
         return readOnly
+
+    def adjustForLinks(self, obj, line, startOffset):
+        """Adjust line to include the word "link" after any hypertext links.
+        Overridden here to deal with parents containing children which in
+        turn contain the links and implement the hypertext interface.
+
+        Arguments:
+        - obj: the accessible object that this line came from.
+        - line: the string to adjust for links.
+        - startOffset: the caret offset at the start of the line.
+
+        Returns: a new line adjusted to add the speaking of "link" after
+        text which is also a link.
+        """
+
+        adjustedLine = script_utilities.Utilities.adjustForLinks(
+                self, obj, line, startOffset)
+
+        roles = [pyatspi.ROLE_LIST_ITEM]
+        if not obj.getRole() in roles or not obj.childCount:
+            return adjustedLine
+
+        child = obj[0]
+        try:
+            hypertext = obj.queryHypertext()
+            nLinks = hypertext.getNLinks()
+        except NotImplementedError:
+            nLinks = 0
+
+        if not nLinks:
+            try:
+                hypertext = child.queryHypertext()
+                nLinks = hypertext.getNLinks()
+            except NotImplementedError:
+                pass
+
+        if not nLinks:
+            return adjustedLine
+
+        try:
+            objText = obj.queryText()
+            childText = child.queryText()
+        except NotImplementedError:
+            return adjustedLine
+
+        adjustment = objText.characterCount - childText.characterCount
+        if adjustment:
+            adjustedLine = script_utilities.Utilities.adjustForLinks(
+                self, child, line, startOffset - adjustment)
+
+        return adjustedLine
diff --git a/src/orca/scripts/toolkits/WebKitGtk/speech_generator.py b/src/orca/scripts/toolkits/WebKitGtk/speech_generator.py
index bb6c464..03bc798 100644
--- a/src/orca/scripts/toolkits/WebKitGtk/speech_generator.py
+++ b/src/orca/scripts/toolkits/WebKitGtk/speech_generator.py
@@ -28,6 +28,7 @@ __license__   = "LGPL"
 import pyatspi
 
 import orca.rolenames as rolenames
+import orca.settings as settings
 import orca.speech_generator as speech_generator
 
 from orca.orca_i18n import _
@@ -44,6 +45,13 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
     def __init__(self, script):
         speech_generator.SpeechGenerator.__init__(self, script)
 
+    def getVoiceForString(self, obj, string, **args):
+        voice = settings.voices[settings.DEFAULT_VOICE]
+        if string.decode("UTF-8").isupper():
+            voice = settings.voices[settings.UPPERCASE_VOICE]
+
+        return voice
+
     def _generateRoleName(self, obj, **args):
         result = []
         acss = self.voice(speech_generator.SYSTEM)
@@ -55,6 +63,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
             doNotSpeak.extend([pyatspi.ROLE_FORM,
                                pyatspi.ROLE_LABEL,
                                pyatspi.ROLE_MENU_ITEM,
+                               pyatspi.ROLE_LIST_ITEM,
                                pyatspi.ROLE_PARAGRAPH,
                                pyatspi.ROLE_SECTION,
                                pyatspi.ROLE_TABLE_CELL])



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]