[orca/570658] Add in speech context as part of formatting and speech generation.
- From: William Walker <wwalker src gnome org>
- To: svn-commits-list gnome org
- Subject: [orca/570658] Add in speech context as part of formatting and speech generation.
- Date: Thu, 14 May 2009 18:23:19 -0400 (EDT)
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]