[orca/570658] Add in speech context as part of formatting and speech generation.



commit f892f0d13d310683aaf6102978e675889d9d27ec
Author: Willie Walker <william walker sun com>
Date:   Thu May 14 18:22:39 2009 -0400

    Add in speech context as part of formatting and speech generation.
---
 src/orca/default.py         |  257 ++++-----------------------
 src/orca/formatting.py      |   97 +++++++----
 src/orca/speechgenerator.py |  414 +++++++++++++++++++++++++++++++++++--------
 3 files changed, 438 insertions(+), 330 deletions(-)

diff --git a/src/orca/default.py b/src/orca/default.py
index 04cec3b..16f9815 100644
--- a/src/orca/default.py
+++ b/src/orca/default.py
@@ -2261,34 +2261,34 @@ class Script(script.Script):
         """Speaks the sentence prior to the caret, as long as there is
         a sentence prior to the caret and there is no intervening sentence
         delimiter between the caret and the end of the sentence.
- 
+
         The entry condition for this method is that the character
         prior to the current caret position is a sentence delimiter,
         and it's what caused this method to be called in the first
         place.
- 
+
         Arguments:
         - obj: an Accessible object that implements the AccessibleText
         interface.
         """
- 
+
         try:
             text = obj.queryText()
         except NotImplementedError:
             return
- 
+
         offset = text.caretOffset - 1
         previousOffset = text.caretOffset - 2
         if (offset < 0 or previousOffset < 0):
             return
- 
+
         [currentChar, startOffset, endOffset] = \
             text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR)
         [previousChar, startOffset, endOffset] = \
             text.getTextAtOffset(previousOffset, pyatspi.TEXT_BOUNDARY_CHAR)
         if not self.isSentenceDelimiter(currentChar, previousChar):
             return
- 
+
         # OK - we seem to be cool so far.  So...starting with what
         # should be the last character in the sentence (caretOffset - 2),
         # work our way to the beginning of the sentence, stopping when
@@ -2296,7 +2296,7 @@ class Script(script.Script):
         #
         sentenceEndOffset = text.caretOffset - 2
         sentenceStartOffset = sentenceEndOffset
- 
+
         while sentenceStartOffset >= 0:
             [currentChar, startOffset, endOffset] = \
                 text.getTextAtOffset(sentenceStartOffset,
@@ -2308,7 +2308,7 @@ class Script(script.Script):
                 break
             else:
                 sentenceStartOffset -= 1
- 
+
         # If we came across a sentence delimiter before hitting any
         # text, we really don't have a previous sentence.
         #
@@ -2323,17 +2323,17 @@ class Script(script.Script):
         else:
             sentence = self.getText(obj, sentenceStartOffset + 1,
                                          sentenceEndOffset + 1)
- 
+
         if self.getLinkIndex(obj, sentenceStartOffset + 1) >= 0:
             voice = self.voices[settings.HYPERLINK_VOICE]
         elif sentence.isupper():
             voice = self.voices[settings.UPPERCASE_VOICE]
         else:
             voice = self.voices[settings.DEFAULT_VOICE]
- 
+
         sentence = self.adjustForRepeats(sentence)
         speech.speak(sentence, voice)
- 
+
     def echoPreviousWord(self, obj, offset=None):
         """Speaks the word prior to the caret, as long as there is
         a word prior to the caret and there is no intervening word
@@ -2776,6 +2776,7 @@ class Script(script.Script):
         # Clear the point of reference.
         # If the point of reference is a cell, we want to keep the
         # table-related points of reference.
+        #
         if oldParent is not None and oldParent == newParent and \
               newParent.getRole() == pyatspi.ROLE_TABLE:
             for key in self.pointOfReference.keys():
@@ -2787,154 +2788,14 @@ class Script(script.Script):
         if newLocusOfFocus:
             self.updateBraille(newLocusOfFocus)
 
-            utterances = []
-
-            # Now figure out how of the container context changed and
-            # speech just what is different.
-            #
-            commonAncestor = self.findCommonAncestor(oldLocusOfFocus,
-                                                     newLocusOfFocus)
-            if commonAncestor:
-                context = self.speechGenerator.getSpeechContext( \
-                                           newLocusOfFocus, commonAncestor)
-                utterances.append(" ".join(context))
-
-            # Now, we'll treat table row and column headers as context
-            # as well.  This requires special handling because we're
-            # making headers seem hierarchical in the context, but they
-            # are not hierarchical in the containment hierarchicy.
-            # We also only want to speak the one that changed.  If both
-            # changed, first speak the row header, then the column header.
-            #
-            # We also keep track of tree level depth and only announce
-            # that if it changes.
-            #
-            # Note that Java Swing allows things like ROLE_LABEL objects
-            # in trees and tables, so we'll check the parent's role to 
-            # see if it is a table.
-            #
-            oldNodeLevel = -1
-            newNodeLevel = -1
-            if (newLocusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL) \
-               or (newParent.getRole() == pyatspi.ROLE_TABLE):
-                try:
-                    table = oldParent.queryTable()
-                except:
-                    table = None
-                if table and \
-                      ((oldLocusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL) \
-                       or (oldParent.getRole() == pyatspi.ROLE_TABLE)):
-                    index = self.getCellIndex(oldLocusOfFocus)
-                    oldRow = table.getRowAtIndex(index)
-                    oldCol = table.getColumnAtIndex(index)
-                else:
-                    oldRow = -1
-                    oldCol = -1
-
-                try:
-                    table = newParent.queryTable()
-                except:
-                    pass
-                else:
-                    index = self.getCellIndex(newLocusOfFocus)
-                    newRow = table.getRowAtIndex(index)
-                    newCol = table.getColumnAtIndex(index)
-
-                    if (newRow >= 0) \
-                       and ((newRow != oldRow) or (oldParent != newParent)):
-                        # Get the header information.  In Java Swing, the
-                        # information is not exposed via the description
-                        # but is instead a header object, so we fall back
-                        # to that if it exists.
-                        #
-                        # [[[TODO: WDW - the more correct thing to do, I 
-                        # think, is to look at the row header object.
-                        # We've been looking at the description for so 
-                        # long, though, that we'll give the description 
-                        # preference for now.]]]
-                        #
-                        desc = table.getRowDescription(newRow)
-                        if not desc:
-                            header = table.getRowHeader(newRow)
-                            if header:
-                                desc = self.getDisplayedText(header)
-                        if desc and len(desc):
-                            text = desc
-                            if settings.speechVerbosityLevel \
-                                   == settings.VERBOSITY_LEVEL_VERBOSE:
-                                text += " " \
-                                        + rolenames.rolenames[\
-                                        pyatspi.ROLE_ROW_HEADER].speech
-                            utterances.append(text)
-                    if (newCol >= 0) \
-                       and ((newCol != oldCol) or (oldParent != newParent)):
-                        # Don't speak Thunderbird column headers, since
-                        # it's not possible to navigate across a row.
-                        topName = self.getTopLevelName(newLocusOfFocus)
-                        if not topName.endswith(" - Thunderbird"):
-                            # Get the header information.  In Java Swing, the
-                            # information is not exposed via the description
-                            # but is instead a header object, so we fall back
-                            # to that if it exists.
-                            #
-                            # [[[TODO: WDW - the more correct thing to do, I 
-                            # think, is to look at the row header object.
-                            # We've been looking at the description for so 
-                            # long, though, that we'll give the description 
-                            # preference for now.]]]
-                            #
-                            desc = table.getColumnDescription(newCol)
-                            if not desc:
-                                header = table.getColumnHeader(newCol)
-                                if header:
-                                    desc = self.getDisplayedText(header)
-                            cellText = self.getDisplayedText(newLocusOfFocus)
-                            if desc and len(desc) and cellText != desc:
-                                text = desc
-                                if settings.speechVerbosityLevel \
-                                       == settings.VERBOSITY_LEVEL_VERBOSE:
-                                    text += " " \
-                                            + rolenames.rolenames[\
-                                            pyatspi.ROLE_COLUMN_HEADER].speech
-                                utterances.append(text)
-
-            oldNodeLevel = self.getNodeLevel(oldLocusOfFocus)
-            newNodeLevel = self.getNodeLevel(newLocusOfFocus)
-
-            # We'll also treat radio button groups as though they are
-            # in a context, with the label for the group being the
-            # name of the context.
-            #
-            if newLocusOfFocus \
-               and newLocusOfFocus.getRole() == pyatspi.ROLE_RADIO_BUTTON:
-                radioGroupLabel = None
-                inSameGroup = False
-                relations = newLocusOfFocus.getRelationSet()
-                for relation in relations:
-                    if (not radioGroupLabel) \
-                        and (relation.getRelationType() \
-                             == pyatspi.RELATION_LABELLED_BY):
-                        radioGroupLabel = relation.getTarget(0)
-                    if (not inSameGroup) \
-                        and (relation.getRelationType() \
-                             == pyatspi.RELATION_MEMBER_OF):
-                        for i in range(0, relation.getNTargets()):
-                            target = relation.getTarget(i)
-                            if target == oldLocusOfFocus:
-                                inSameGroup = True
-                                break
-
-                # We'll only announce the radio button group when we
-                # switch groups.
-                #
-                if (not inSameGroup) and radioGroupLabel:
-                    utterances.append(self.getDisplayedText(radioGroupLabel))
-
             # Check to see if we are in the Pronunciation Dictionary in the
             # Orca Preferences dialog. If so, then we do not want to use the
             # pronunciation dictionary to replace the actual words in the
             # first column of this table.
             #
+            # [[[TODO: WDW - this should be pushed into an
+            # adjustForPronunciation method in a script for orca.]]]
+            #
             rolesList = [pyatspi.ROLE_TABLE_CELL, \
                          pyatspi.ROLE_TABLE, \
                          pyatspi.ROLE_SCROLL_PANE, \
@@ -2946,59 +2807,6 @@ class Script(script.Script):
             else:
                 orca_state.usePronunciationDictionary = True
 
-            # Get the text for the object itself.
-            #
-            utterances.extend(
-                self.speechGenerator.getSpeech(newLocusOfFocus))
-            utterances.extend(
-                self.tutorialGenerator.getTutorial(newLocusOfFocus, False))
-            # Now speak the new tree node level if it has changed.
-            #
-            if (oldNodeLevel != newNodeLevel) \
-               and (newNodeLevel >= 0):
-                # Translators: this represents the depth of a node in a tree
-                # view (i.e., how many ancestors a node has).
-                #
-                utterances.append(_("tree level %d") % (newNodeLevel + 1))
-
-            # If this is an icon within an layered pane or a table cell
-            # within a table or a tree table and the item is focused but not
-            # selected, let the user know. See bug #486908 for more details.
-            #
-            checkIfSelected = False
-            objRole, parentRole, state = None, None, None
-            if newLocusOfFocus:
-                objRole = newLocusOfFocus.getRole()
-                state = newLocusOfFocus.getState()
-                if newLocusOfFocus.parent:
-                    parentRole = newLocusOfFocus.parent.getRole()
-
-            if objRole == pyatspi.ROLE_TABLE_CELL and \
-               (parentRole == pyatspi.ROLE_TREE_TABLE or \
-                parentRole == pyatspi.ROLE_TABLE):
-                checkIfSelected = True
-
-            # If we met the last set of conditions, but we got here by
-            # moving left or right on the same row, then don't announce the
-            # selection state to the user. See bug #523235 for more details.
-            #
-            if checkIfSelected == True and \
-               (orca_state.lastNonModifierKeyEvent and \
-               (orca_state.lastNonModifierKeyEvent.event_string == "Left" or \
-               orca_state.lastNonModifierKeyEvent.event_string == "Right")):
-                checkIfSelected = False
-
-            if objRole == pyatspi.ROLE_ICON and \
-                parentRole == pyatspi.ROLE_LAYERED_PANE:
-                checkIfSelected = True
-
-            if checkIfSelected and state \
-               and not state.contains(pyatspi.STATE_SELECTED):
-                # Translators: this is in reference to a table cell being
-                # selected or not.
-                #
-                utterances.append(C_("tablecell", " not selected"))
-
             # We might be automatically speaking the unbound labels
             # in a dialog box as the result of the dialog box suddenly
             # appearing.  If so, don't interrupt this because of a
@@ -3009,18 +2817,23 @@ class Script(script.Script):
                 and self.windowActivateTime \
                 and ((time.time() - self.windowActivateTime) < 1.0)
 
-            if objRole == pyatspi.ROLE_LINK:
+            # [[[TODO: WDW - this should move to the generator.]]]
+            #
+            if newLocusOfFocus.getRole() == pyatspi.ROLE_LINK:
                 voice = self.voices[settings.HYPERLINK_VOICE]
             else:
                 voice = self.voices[settings.DEFAULT_VOICE]
 
+            utterances = self.speechGenerator.getSpeech(
+                newLocusOfFocus,
+                priorObj=oldLocusOfFocus)
             speech.speak(utterances, voice, not shouldNotInterrupt)
 
             # If this is a table cell, save the current row and column
             # information in the table cell's table, so that we can use
             # it the next time.
             #
-            if objRole == pyatspi.ROLE_TABLE_CELL:
+            if newLocusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL:
                 try:
                     table = newParent.queryTable()
                 except:
@@ -3323,7 +3136,7 @@ class Script(script.Script):
 
     def _speakContiguousSelection(self, obj, relationship):
         """Check if the contiguous object has a selection. If it does, then
-        speak it. If the user pressed Shift-Down, then look for an object 
+        speak it. If the user pressed Shift-Down, then look for an object
         with a RELATION_FLOWS_FROM relationship. If they pressed Shift-Up,
         then look for a RELATION_FLOWS_TO relationship.
 
@@ -3359,7 +3172,7 @@ class Script(script.Script):
 
                 # When selecting down across paragraph boundaries, what
                 # we've (un)selected on (what is now) the previous line
-                # is from wherever the cursor used to be to the end of 
+                # is from wherever the cursor used to be to the end of
                 # the line.
                 #
                 if relationship == pyatspi.RELATION_FLOWS_FROM:
@@ -3396,7 +3209,7 @@ class Script(script.Script):
         return selSpoken
 
     def _presentTextAtNewCaretPosition(self, event, otherObj=None):
-        """Updates braille, magnification, and outputs speech for the 
+        """Updates braille, magnification, and outputs speech for the
         event.source or the otherObj."""
 
         obj = otherObj or event.source
@@ -3458,7 +3271,7 @@ class Script(script.Script):
                     # Shift+Up, what we've selected in this object starts
                     # with the current offset and goes to the end of the
                     # paragraph.
-                    # 
+                    #
                     if not self.isSameObject(lastPos[0], obj):
                         [startOffset, endOffset] = \
                             text.caretOffset, text.characterCount
@@ -3762,10 +3575,10 @@ class Script(script.Script):
         except NotImplementedError:
             return
 
-        # Pylint is confused and flags this and similar lines, with the 
+        # Pylint is confused and flags this and similar lines, with the
         # following error:
         #
-        # E1103:3673:Script.onTextInserted: Instance of 'str' has no 
+        # E1103:3673:Script.onTextInserted: Instance of 'str' has no
         #'caretOffset' member (but some types could not be inferred)
         #
         # But it does, so we'll just tell pylint that we know what we
@@ -4088,7 +3901,7 @@ class Script(script.Script):
 
             # If there's a completely blank line in between our previous
             # and current locations, where we came from will lack any
-            # offically-selectable characters. As a result, we won't 
+            # offically-selectable characters. As a result, we won't
             # indicate when a blank line has been selected. Under these
             # conditions, we'll try to backtrack further.
             #
@@ -4108,8 +3921,8 @@ class Script(script.Script):
                     else:
                         break
 
-        self.speakTextSelectionState(obj, startOffset, endOffset)    
-            
+        self.speakTextSelectionState(obj, startOffset, endOffset)
+
     def onSelectionChanged(self, event):
         """Called when an object's selection changes.
 
@@ -4681,7 +4494,7 @@ class Script(script.Script):
             table = parent.queryTable()
         except NotImplementedError:
             table = None
-            
+
         if table:
             for i in range(0, table.nColumns):
                 header = table.getColumnHeader(i)
@@ -6104,7 +5917,7 @@ class Script(script.Script):
         Returns a string representing the value.
         """
 
-        # Use ARIA "valuetext" attribute if present.  See 
+        # Use ARIA "valuetext" attribute if present.  See
         # http://bugzilla.gnome.org/show_bug.cgi?id=552965
         #
         attributes = obj.getAttributes()
@@ -6647,8 +6460,8 @@ class Script(script.Script):
 
     def isSentenceDelimiter(self, currentChar, previousChar):
         """Returns True if we are positioned at the end of a sentence.
-        This is determined by checking if the current character is a 
-        white space character and the previous character is one of the 
+        This is determined by checking if the current character is a
+        white space character and the previous character is one of the
         normal end-of-sentence punctuation characters.
 
         Arguments:
diff --git a/src/orca/formatting.py b/src/orca/formatting.py
index fafc0bb..ada891c 100644
--- a/src/orca/formatting.py
+++ b/src/orca/formatting.py
@@ -29,10 +29,18 @@ import pyatspi
 
 # pylint: disable-msg=C0301
 
-defaultFormatting = {
+formatting = {
     'speech': {
+        'prefix': {
+            'focused': '[]',
+            'unfocused': 'newAncestors + newRowHeader + newColumnHeader + newRadioButtonGroup'
+            },
+        'suffix': {
+            'focused': '[]',
+            'unfocused': 'newNodeLevel + unselectedCell + tutorial'
+            },
         'default': {
-            'focused': '',
+            'focused': '[]',
             'unfocused': 'labelAndName + allTextSelection + roleName + availability'
             },
         pyatspi.ROLE_ALERT: {
@@ -53,7 +61,7 @@ defaultFormatting = {
             'unfocused': 'labelAndName + unrelatedLabels'
             },
         pyatspi.ROLE_FRAME: {
-            'focused': '',
+            'focused': '[]',
             'unfocused': 'labelAndName + allTextSelection + roleName + unfocusedDialogCount + availability'
             },
         pyatspi.ROLE_ICON: {
@@ -69,11 +77,11 @@ defaultFormatting = {
             'unfocused': 'labelAndName + allTextSelection + expandableState + availability'
             },
         pyatspi.ROLE_MENU: {
-            'focused': '',
+            'focused': '[]',
             'unfocused': 'labelAndName + allTextSelection + roleName + availability'
             },
         pyatspi.ROLE_MENU_ITEM: {
-            'focused': '',
+            'focused': '[]',
             'unfocused': 'labelAndName + menuItemCheckedState + availability + accelerator'
             },
         pyatspi.ROLE_PASSWORD_TEXT: {
@@ -85,16 +93,7 @@ defaultFormatting = {
             'unfocused': 'labelAndName + percentage'
             },
         pyatspi.ROLE_PUSH_BUTTON: {
-            # [[[TODO: WDW - this is just an example of embedding a
-            # voice in the format.  It should be removed when we've
-            # figured that stuff out.]]]
-            #
-            # [[[NOTE: MMH - We will only hear the tutorial string once per script,
-            # this is because the code checks if the tutorial to be spoken was the last tutorial, and this will always be true.
-            # Just putting 'tutorial' in some of the other unfocused strings should get it to behave as normally.
-            # Also r==s thing wont be true when returning tutorial msges because the old getSpeech was not responsible for tutorials, but now it is.]]]
-            #
-            'unfocused': 'voice(role) + labelAndName + [voice("uppercase") + roleName] + tutorial'
+            'unfocused': 'labelAndName + roleName'
             },
         pyatspi.ROLE_RADIO_BUTTON: {
             'focused': 'radioState',
@@ -151,7 +150,7 @@ defaultFormatting = {
             'unfocused': '(tableCell2ChildLabel + tableCell2ChildToggle) or cellCheckedState + (realActiveDescendantDisplayedText or imageDescription + image) + (expandableState and (expandableState + numberOfChildren)) + required'
             },
         pyatspi.ROLE_TEAROFF_MENU_ITEM: {
-            'focused': '',
+            'focused': '[]',
             'unfocused': 'labelAndName + allTextSelection + roleName + availability'
             },
         pyatspi.ROLE_TERMINAL: {
@@ -182,27 +181,14 @@ class Formatting(dict):
     def __init__(self, script):
         dict.__init__(self)
         self._script = script
-        self.update(defaultFormatting)
-
-    def getFormat(self, dictType, **args):
-        already_focused = args.get('already_focused', False)
-        role = args.get('role', None)
-        if self[dictType].has_key(role):
-            roleDict = self[dictType][role]
-        else:
-            roleDict = self[dictType]['default']
-        if already_focused and 'focused' in roleDict:
-            format = roleDict['focused']
-        else:
-            format = roleDict['unfocused']
-        return format
+        self.update(formatting)
 
     def update(self, newDict):
         for key, val in newDict.iteritems():
             if self.has_key(key):
-                if isinstance(self[key], dict) and isinstance(val, dict): 
+                if isinstance(self[key], dict) and isinstance(val, dict):
                     self[key].update(val)
-                elif isinstance(self[key], basestring) and isinstance(val, basestring): 
+                elif isinstance(self[key], basestring) and isinstance(val, basestring):
                     self[key] = val
                 else:
                     # exception or such like, we are trying to murge
@@ -211,3 +197,50 @@ class Formatting(dict):
                     print("an error has occured, cant murge dicts.")
             else:
                 self[key] = val
+
+    def getPrefix(self, dictType, **args):
+        already_focused = args.get('already_focused', False)
+        if already_focused:
+            focusType = 'focused'
+        else:
+            focusType = 'unfocused'
+        try:
+            prefix = self[dictType]['prefix'][focusType]
+        except:
+            prefix = self[dictType]['prefix']['unfocused']
+        return prefix
+
+    def getSuffix(self, dictType, **args):
+        already_focused = args.get('already_focused', False)
+        if already_focused:
+            focusType = 'focused'
+        else:
+            focusType = 'unfocused'
+        try:
+            suffix = self[dictType]['suffix'][focusType]
+        except:
+            suffix = self[dictType]['suffix']['unfocused']
+        return suffix
+
+    def getFormat(self, dictType, **args):
+        already_focused = args.get('already_focused', False)
+        if already_focused:
+            focusType = 'focused'
+        else:
+            focusType = 'unfocused'
+
+        role = args.get('role', None)
+        try:
+            roleDict = self[dictType][role]
+        except:
+            roleDict = self[dictType]['default']
+
+        try:
+            format = roleDict[focusType]
+        except:
+            try:
+                format = roleDict['unfocused']
+            except:
+                format = self[dictType]['default'][focusType]
+
+        return format
diff --git a/src/orca/speechgenerator.py b/src/orca/speechgenerator.py
index e0bbb07..adb81dc 100644
--- a/src/orca/speechgenerator.py
+++ b/src/orca/speechgenerator.py
@@ -31,6 +31,7 @@ import sys
 import traceback
 
 import debug
+import orca_state
 import pyatspi
 import rolenames
 import settings
@@ -66,6 +67,8 @@ class SpeechGenerator:
     entry point.  Subclasses can feel free to override/extend the
     speechGenerators instance field as they see fit."""
 
+    # pylint: disable-msg=W0142
+
     def __init__(self, script):
         self._script = script
         self._methodsDict = {}
@@ -87,9 +90,10 @@ class SpeechGenerator:
         methods = {}
         for key in self._methodsDict.keys():
             methods[key] = []
-        methods["voice"] = self.voice
-        methods["obj"] = None
-        methods["role"] = None
+        methods['voice'] = self.voice
+        methods['obj'] = None
+        methods['role'] = None
+        methods['pyatspi'] = pyatspi
         for roleKey in self._script.formatting["speech"]:
             for speechKey in ["focused", "unfocused"]:
                 try:
@@ -112,7 +116,8 @@ class SpeechGenerator:
                             arg = arg.replace("name '", "")
                             arg = arg.replace("' is not defined", "")
                             if not self._methodsDict.has_key(arg):
-                                debug.printException(
+                                debug.printException(debug.LEVEL_SEVERE)
+                                debug.println(
                                     debug.LEVEL_SEVERE,
                                     "Unable to find function for '%s'\n" % arg)
                         except:
@@ -140,7 +145,6 @@ class SpeechGenerator:
 
     def _getTextRole(self, obj, **args):
         result = []
-        # pylint: disable-msg=W0142
         role = args.get('role', obj.getRole())
         if role != pyatspi.ROLE_PARAGRAPH:
             result.extend(self._getRoleName(obj, **args))
@@ -163,7 +167,6 @@ class SpeechGenerator:
     def _getLabelAndName(self, obj, **args):
         """Gets the label and the name if the name is different from the label.
         """
-        # pylint: disable-msg=W0142
         result = []
         label = self._getLabel(obj, **args)
         name = self._getName(obj, **args)
@@ -177,7 +180,6 @@ class SpeechGenerator:
     def _getLabelOrName(self, obj, **args):
         """Gets the label or the name if the label is not preset."""
         result = []
-        # pylint: disable-msg=W0142
         result.extend(self._getLabel(obj, **args))
         if not result:
             if obj.name and (len(obj.name)):
@@ -186,7 +188,6 @@ class SpeechGenerator:
 
     def _getUnrelatedLabels(self, obj, **args):
         """Finds all labels not in a label for or labelled by relation."""
-        # pylint: disable-msg=W0142
         labels = self._script.findUnrelatedLabels(obj)
         result = []
         for label in labels:
@@ -195,7 +196,6 @@ class SpeechGenerator:
         return result
 
     def _getEmbedded(self, obj, **args):
-        # pylint: disable-msg=W0142
         result = self._getLabelOrName(obj, **args)
         if not result:
             try:
@@ -228,7 +228,6 @@ class SpeechGenerator:
         return result
 
     def _getCellCheckedState(self, obj, **args):
-        # pylint: disable-msg=W0142
         result = []
         try:
             action = obj.queryAction()
@@ -362,10 +361,162 @@ class SpeechGenerator:
     #                                                                   #
     #####################################################################
 
+    def _getRowHeader(self, obj, **args):
+        result = []
+        try:
+            table = obj.parent.queryTable()
+        except:
+            pass
+        else:
+            index = self._script.getCellIndex(obj)
+            rowIndex = table.getRowAtIndex(index)
+            if rowIndex >= 0:
+                # Get the header information.  In Java Swing, the
+                # information is not exposed via the description
+                # but is instead a header object, so we fall back
+                # to that if it exists.
+                #
+                # [[[TODO: WDW - the more correct thing to do, I
+                # think, is to look at the row header object.
+                # We've been looking at the description for so
+                # long, though, that we'll give the description
+                # preference for now.]]]
+                #
+                desc = table.getRowDescription(rowIndex)
+                if not desc:
+                    header = table.getRowHeader(rowIndex)
+                    if header:
+                        desc = self._script.getDisplayedText(header)
+                if desc and len(desc):
+                    text = desc
+                    if settings.speechVerbosityLevel \
+                            == settings.VERBOSITY_LEVEL_VERBOSE:
+                        text += " " \
+                            + rolenames.rolenames[\
+                            pyatspi.ROLE_ROW_HEADER].speech
+                    result.append(text)
+        return result
+
+    def _getNewRowHeader(self, obj, **args):
+        """Returns the row header for the object only if the
+        row header changed from the prior object."""
+        result = []
+        if obj:
+            priorObj = args.get('priorObj', None)
+            try:
+                priorParent = priorObj.parent
+            except:
+                priorParent = None
+
+            if (obj.getRole() == pyatspi.ROLE_TABLE_CELL) \
+                or (obj.parent.getRole() == pyatspi.ROLE_TABLE):
+                try:
+                    table = priorParent.queryTable()
+                except:
+                    table = None
+                if table \
+                   and ((priorObj.getRole() == pyatspi.ROLE_TABLE_CELL) \
+                         or (priorObj.getRole() == pyatspi.ROLE_TABLE)):
+                    index = self._script.getCellIndex(priorObj)
+                    oldRow = table.getRowAtIndex(index)
+                else:
+                    oldRow = -1
+
+                try:
+                    table = obj.parent.queryTable()
+                except:
+                    pass
+                else:
+                    index = self._script.getCellIndex(obj)
+                    newRow = table.getRowAtIndex(index)
+                    if (newRow >= 0) \
+                       and ((newRow != oldRow) \
+                            or (obj.parent != priorParent)):
+                        result = self._getRowHeader(obj, **args)
+        return result
+
+    def _getColumnHeader(self, obj, **args):
+        result = []
+        try:
+            table = obj.parent.queryTable()
+        except:
+            pass
+        else:
+            index = self._script.getCellIndex(obj)
+            columnIndex = table.getColumnAtIndex(index)
+            # Don't speak Thunderbird column headers, since
+            # it's not possible to navigate across a row.
+            # [[[TODO: WDW - move the T-bird check to the T-bird generator.]]]
+            if (columnIndex >= 0) \
+               and not self._script.getTopLevelName(obj).endswith(
+                " - Thunderbird"):
+                # Get the header information.  In Java Swing, the
+                # information is not exposed via the description
+                # but is instead a header object, so we fall back
+                # to that if it exists.
+                #
+                # [[[TODO: WDW - the more correct thing to do, I
+                # think, is to look at the row header object.
+                # We've been looking at the description for so
+                # long, though, that we'll give the description
+                # preference for now.]]]
+                #
+                desc = table.getColumnDescription(columnIndex)
+                if not desc:
+                    header = table.getColumnHeader(columnIndex)
+                    if header:
+                        desc = self._script.getDisplayedText(header)
+                if desc and len(desc):
+                    text = desc
+                    if settings.speechVerbosityLevel \
+                            == settings.VERBOSITY_LEVEL_VERBOSE:
+                        text += " " \
+                            + rolenames.rolenames[\
+                            pyatspi.ROLE_COLUMN_HEADER].speech
+                    result.append(text)
+        return result
+
+    def _getNewColumnHeader(self, obj, **args):
+        """Returns the column header for the object only if the
+        column header changed from the prior object."""
+        result = []
+        if obj:
+            priorObj = args.get('priorObj', None)
+            try:
+                priorParent = priorObj.parent
+            except:
+                priorParent = None
+
+            if (obj.getRole() == pyatspi.ROLE_TABLE_CELL) \
+                or (obj.parent.getRole() == pyatspi.ROLE_TABLE):
+                try:
+                    table = priorParent.queryTable()
+                except:
+                    table = None
+                if table \
+                   and ((priorObj.getRole() == pyatspi.ROLE_TABLE_CELL) \
+                         or (priorObj.getRole() == pyatspi.ROLE_TABLE)):
+                    index = self._script.getCellIndex(priorObj)
+                    oldCol = table.getColumnAtIndex(index)
+                else:
+                    oldCol = -1
+
+                try:
+                    table = obj.parent.queryTable()
+                except:
+                    pass
+                else:
+                    index = self._script.getCellIndex(obj)
+                    newCol = table.getColumnAtIndex(index)
+                    if (newCol >= 0) \
+                       and ((newCol != oldCol) \
+                            or (obj.parent != priorParent)):
+                        result = self._getColumnHeader(obj, **args)
+        return result
+
     def _getTableCell2ChildLabel(self, obj, **args):
         """Get the speech utterances for the label of a toggle in a table cell
         that has a special 2 child pattern that we run into."""
-        # pylint: disable-msg=W0142
         result = []
 
         # If this table cell has 2 children and one of them has a
@@ -405,7 +556,6 @@ class SpeechGenerator:
     def _getTableCell2ChildToggle(self, obj, **args):
         """Get the speech utterances for the toggle value in a table cell that
         has a special 2 child pattern that we run into."""
-        # pylint: disable-msg=W0142
         result = []
 
         # If this table cell has 2 children and one of them has a
@@ -446,7 +596,6 @@ class SpeechGenerator:
     def _getTableCellRow(self, obj, **args):
         """Get the speech for a table cell row or a single table cell
         if settings.readTableCellRow is False."""
-        # pylint: disable-msg=W0142
         result = []
 
         try:
@@ -522,6 +671,48 @@ class SpeechGenerator:
             _restoreRole(oldRole, args)
         return result
 
+    def _getUnselectedCell(self, obj, **args):
+        result = []
+
+        # If this is an icon within an layered pane or a table cell
+        # within a table or a tree table and the item is focused but not
+        # selected, let the user know. See bug #486908 for more details.
+        #
+        checkIfSelected = False
+        objRole, parentRole, state = None, None, None
+        if obj:
+            objRole = obj.getRole()
+            state = obj.getState()
+            if obj.parent:
+                parentRole = obj.parent.getRole()
+
+        if objRole == pyatspi.ROLE_TABLE_CELL \
+           and (parentRole == pyatspi.ROLE_TREE_TABLE \
+                or parentRole == pyatspi.ROLE_TABLE):
+            checkIfSelected = True
+
+        # If we met the last set of conditions, but we got here by
+        # moving left or right on the same row, then don't announce the
+        # selection state to the user. See bug #523235 for more details.
+        #
+        if checkIfSelected and orca_state.lastNonModifierKeyEvent \
+           and orca_state.lastNonModifierKeyEvent.event_string \
+               in ["Left", "Right"]:
+            checkIfSelected = False
+
+        if objRole == pyatspi.ROLE_ICON \
+           and parentRole == pyatspi.ROLE_LAYERED_PANE:
+            checkIfSelected = True
+
+        if checkIfSelected \
+           and state and not state.contains(pyatspi.STATE_SELECTED):
+            # Translators: this is in reference to a table cell being
+            # selected or not.
+            #
+            result.append(C_("tablecell", "not selected"))
+
+        return result
+
     #####################################################################
     #                                                                   #
     # Terminal information                                              #
@@ -576,6 +767,37 @@ class SpeechGenerator:
 
     #####################################################################
     #                                                                   #
+    # Tree interface information                                        #
+    #                                                                   #
+    #####################################################################
+
+    def _getNodeLevel(self, obj, **args):
+        result = []
+        level = self._script.getNodeLevel(obj)
+        if level >= 0:
+            # Translators: this represents the depth of a node in a tree
+            # view (i.e., how many ancestors a node has).
+            #
+            result.append(_("tree level %d") % (level + 1))
+        return result
+
+    def _getNewNodeLevel(self, obj, **args):
+        # [[[TODO: WDW - hate duplicating code from _getNodeLevel,
+        # but don't want to call it because it will make the same
+        # self._script.getNodeLevel call again.]]]
+        #
+        result = []
+        oldLevel = self._script.getNodeLevel(args.get('priorObj', None))
+        newLevel = self._script.getNodeLevel(obj)
+        if (oldLevel != newLevel) and (newLevel >= 0):
+            # Translators: this represents the depth of a node in a tree
+            # view (i.e., how many ancestors a node has).
+            #
+            result.append(_("tree level %d") % (newLevel + 1))
+        return result
+
+    #####################################################################
+    #                                                                   #
     # Value interface information                                       #
     #                                                                   #
     #####################################################################
@@ -606,6 +828,49 @@ class SpeechGenerator:
     #                                                                   #
     #####################################################################
 
+    def _getRadioButtonGroup(self, obj, **args):
+        result = []
+        if obj.getRole() == pyatspi.ROLE_RADIO_BUTTON:
+            radioGroupLabel = None
+            relations = obj.getRelationSet()
+            for relation in relations:
+                if (not radioGroupLabel) \
+                    and (relation.getRelationType() \
+                         == pyatspi.RELATION_LABELLED_BY):
+                    radioGroupLabel = relation.getTarget(0)
+                    break
+            if radioGroupLabel:
+                result.append(self._script.getDisplayedText(radioGroupLabel))
+        return result
+
+    def _getNewRadioButtonGroup(self, obj, **args):
+        # [[[TODO: WDW - hate duplicating code from _getRadioButtonGroup
+        # but don't want to call it because it will make the same
+        # AT-SPI method calls.]]]
+        #
+        result = []
+        priorObj = args.get('priorObj', None)
+        if obj and obj.getRole() == pyatspi.ROLE_RADIO_BUTTON:
+            radioGroupLabel = None
+            inSameGroup = False
+            relations = obj.getRelationSet()
+            for relation in relations:
+                if (not radioGroupLabel) \
+                    and (relation.getRelationType() \
+                         == pyatspi.RELATION_LABELLED_BY):
+                    radioGroupLabel = relation.getTarget(0)
+                if (not inSameGroup) \
+                    and (relation.getRelationType() \
+                         == pyatspi.RELATION_MEMBER_OF):
+                    for i in range(0, relation.getNTargets()):
+                        target = relation.getTarget(i)
+                        if target == priorObj:
+                            inSameGroup = True
+                            break
+            if (not inSameGroup) and radioGroupLabel:
+                result.append(self._script.getDisplayedText(radioGroupLabel))
+        return result
+
     def _getRealActiveDescendantDisplayedText(self, obj, **args ):
         text = self._script.getDisplayedText(
           self._script.getRealActiveDescendant(obj))
@@ -667,6 +932,41 @@ class SpeechGenerator:
                             alertAndDialogCount) % alertAndDialogCount)
         return result
 
+    def _getAncestors(self, obj, **args):
+        result = []
+        priorObj = args.get('priorObj', None)
+        commonAncestor = self._script.findCommonAncestor(priorObj, obj)
+        if obj != commonAncestor:
+            parent = obj.parent
+            if parent \
+                and (obj.getRole() == pyatspi.ROLE_TABLE_CELL) \
+                and (parent.getRole() == pyatspi.ROLE_TABLE_CELL):
+                parent = parent.parent
+            while parent and (parent.parent != parent):
+                if parent == commonAncestor:
+                    break
+                if not self._script.isLayoutOnly(parent):
+                    text = self._script.getDisplayedLabel(parent)
+                    if not text and 'Text' in pyatspi.listInterfaces(parent):
+                        text = self._script.getDisplayedText(parent)
+                    if text and len(text.strip()):
+                        # Push announcement of cell to the end
+                        #
+                        if parent.getRole() not in [pyatspi.ROLE_TABLE_CELL,
+                                                    pyatspi.ROLE_FILLER]:
+                            result.extend(self._getRoleName(parent))
+                        result.append(text)
+                        if parent.getRole() == pyatspi.ROLE_TABLE_CELL:
+                            result.extend(self._getRoleName(parent))
+                parent = parent.parent
+        return result.reverse() or result
+
+    def _getNewAncestors(self, obj, **args):
+        result = []
+        if args.get('priorObj', None):
+            result = self._getAncestors(obj, **args)
+        return result
+
     #####################################################################
     #                                                                   #
     # Keyboard shortcut information                                     #
@@ -716,63 +1016,6 @@ class SpeechGenerator:
 
     #####################################################################
     #                                                                   #
-    # Get the context of where the object is.                           #
-    #                                                                   #
-    #####################################################################
-
-    def _getContext(self, obj, stopAncestor=None):
-        """Get the information that describes the names and role of
-        the container hierarchy of the object, stopping at and
-        not including the stopAncestor.
-
-        Arguments:
-        - obj: the object
-        - stopAncestor: the anscestor to stop at and not include (None
-          means include all ancestors)
-
-        """
-
-        result = []
-
-        if not obj or obj == stopAncestor:
-            return result
-
-        parent = obj.parent
-        if parent \
-            and (obj.getRole() == pyatspi.ROLE_TABLE_CELL) \
-            and (parent.getRole() == pyatspi.ROLE_TABLE_CELL):
-            parent = parent.parent
-
-        while parent and (parent.parent != parent):
-            if parent == stopAncestor:
-                break
-            if not self._script.isLayoutOnly(parent):
-                text = self._script.getDisplayedLabel(parent)
-                if not text and 'Text' in pyatspi.listInterfaces(parent):
-                    text = self._script.getDisplayedText(parent)
-                if text and len(text.strip()):
-                    # Push announcement of cell to the end
-                    #
-                    if parent.getRole() not in [pyatspi.ROLE_TABLE_CELL,
-                                                pyatspi.ROLE_FILLER]:
-                        result.extend(self._getRoleName(parent))
-                    result.append(text)
-                    if parent.getRole() == pyatspi.ROLE_TABLE_CELL:
-                        result.extend(self._getRoleName(parent))
-
-            parent = parent.parent
-
-        result.reverse()
-
-        return result
-
-    # [[[TODO: WDW - need to figure out the context stuff still.
-    #
-    def getSpeechContext(self, obj, stopAncestor=None):
-        return self._getContext(obj, stopAncestor)
-
-    #####################################################################
-    #                                                                   #
     # Tie it all together                                               #
     #                                                                   #
     #####################################################################
@@ -785,19 +1028,18 @@ class SpeechGenerator:
         return [voice]
 
     def getSpeech(self, obj, **args):
-        # pylint: disable-msg=W0142
         result = []
         methods = {}
-        methods["voice"] = self.voice
-        methods["obj"] = obj
-        methods["role"] = args.get('role', obj.getRole())
+        methods['voice'] = self.voice
+        methods['obj'] = obj
         methods['pyatspi'] = pyatspi
+        methods['role'] = args.get('role', obj.getRole())
 
         try:
             # We sometimes want to override the role.  We'll keep the
             # role in the args dictionary as a means to let us do so.
             #
-            args['role'] = methods["role"]
+            args['role'] = methods['role']
 
             # We loop through the format string, catching each error
             # as we go.  Each error should always be a NameError,
@@ -808,6 +1050,22 @@ class SpeechGenerator:
             #
             format = self._script.formatting.getFormat('speech',
                                                        **args)
+
+            # Add in the speech context if this is the first time
+            # we've been called.
+            #
+            if not args.get('recursing', False):
+                prefix = self._script.formatting.getPrefix('speech',
+                                                           **args)
+                suffix = self._script.formatting.getSuffix('speech',
+                                                           **args)
+                format = '%s + %s + %s' % (prefix, format, suffix)
+                args['recursing'] = True
+                firstTimeCalled = True
+                debug.println(debug.LEVEL_ALL, "getSpeech using '%s'" % format)
+            else:
+                firstTimeCalled = False
+
             assert(format)
             while True:
                 try:
@@ -820,7 +1078,8 @@ class SpeechGenerator:
                     arg = arg.replace("name '", "")
                     arg = arg.replace("' is not defined", "")
                     if not self._methodsDict.has_key(arg):
-                        debug.printException(
+                        debug.printException(debug.LEVEL_SEVERE)
+                        debug.println(
                             debug.LEVEL_SEVERE,
                             "Unable to find function for '%s'\n" % arg)
                         break
@@ -829,4 +1088,7 @@ class SpeechGenerator:
             debug.printException(debug.LEVEL_SEVERE)
             result = []
 
+        if firstTimeCalled:
+            debug.println(debug.LEVEL_ALL,
+                          "getSpeech generated '%s'" % repr(result))
         return result



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