[orca] Fix for bgo#618165 - Create a Utilities class for scripts; Fix for bgo#618166 - Orca's method names
- From: Joanmarie Diggs <joanied src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [orca] Fix for bgo#618165 - Create a Utilities class for scripts; Fix for bgo#618166 - Orca's method names
- Date: Sun, 9 May 2010 12:09:11 +0000 (UTC)
commit b5e4d61bfe379ae1aa2842cb03d3d0e33a5e08be
Author: Joanmarie Diggs <joanmarie diggs gmail com>
Date: Thu May 6 21:10:32 2010 -0400
Fix for bgo#618165 - Create a Utilities class for scripts; Fix for bgo#618166 - Orca's method names are inconsistent and sometimes confusing
configure.in | 1 +
po/POTFILES.in | 1 +
src/orca/Makefile.am | 1 +
src/orca/bookmarks.py | 4 +-
src/orca/braille.py | 14 +-
src/orca/braille_generator.py | 28 +-
src/orca/chat.py | 29 +-
src/orca/default.py | 7053 ++++++--------------
src/orca/flat_review.py | 42 +-
src/orca/focus_tracking_presenter.py | 2 +-
src/orca/generator.py | 49 +-
src/orca/gnomespeechfactory.py | 3 +-
src/orca/liveregions.py | 21 +-
src/orca/mouse_review.py | 4 +-
src/orca/orca.py | 2 +-
src/orca/orca_gui_prefs.py | 12 +-
src/orca/script.py | 7 +
src/orca/script_utilities.py | 2728 ++++++++
src/orca/scripts/apps/Banshee/Makefile.am | 1 +
src/orca/scripts/apps/Banshee/script.py | 37 +-
src/orca/scripts/apps/Banshee/script_utilities.py | 40 +
src/orca/scripts/apps/Instantbird/Makefile.am | 3 +-
src/orca/scripts/apps/Instantbird/chat.py | 37 +-
src/orca/scripts/apps/Instantbird/script.py | 41 +-
.../scripts/apps/Instantbird/script_utilities.py | 100 +
src/orca/scripts/apps/Makefile.am | 2 +-
src/orca/scripts/apps/Thunderbird/Makefile.am | 1 +
src/orca/scripts/apps/Thunderbird/script.py | 62 +-
.../scripts/apps/Thunderbird/script_utilities.py | 88 +
.../scripts/apps/Thunderbird/speech_generator.py | 2 +-
src/orca/scripts/apps/acroread.py | 15 +-
src/orca/scripts/apps/ddu.py | 218 -
src/orca/scripts/apps/ddu/Makefile.am | 8 +
src/orca/scripts/apps/ddu/__init__.py | 23 +
src/orca/scripts/apps/ddu/script.py | 154 +
src/orca/scripts/apps/ddu/script_utilities.py | 142 +
src/orca/scripts/apps/ekiga.py | 5 +-
src/orca/scripts/apps/empathy/Makefile.am | 3 +-
src/orca/scripts/apps/empathy/script.py | 29 +-
src/orca/scripts/apps/empathy/script_utilities.py | 73 +
src/orca/scripts/apps/evolution/Makefile.am | 1 +
src/orca/scripts/apps/evolution/script.py | 1080 +--
.../scripts/apps/evolution/script_utilities.py | 487 ++
.../scripts/apps/evolution/speech_generator.py | 19 +-
src/orca/scripts/apps/gajim/script.py | 3 +-
src/orca/scripts/apps/gcalctool/script.py | 22 +-
.../scripts/apps/gcalctool/speech_generator.py | 2 +-
src/orca/scripts/apps/gedit/script.py | 36 +-
src/orca/scripts/apps/gnome-mud.py | 2 +-
src/orca/scripts/apps/gnome-search-tool.py | 11 +-
src/orca/scripts/apps/gnome-system-monitor.py | 7 +-
src/orca/scripts/apps/gnome-terminal.py | 7 +-
src/orca/scripts/apps/gtk-window-decorator.py | 2 +-
src/orca/scripts/apps/liferea.py | 8 +-
src/orca/scripts/apps/metacity.py | 2 +-
src/orca/scripts/apps/nautilus.py | 39 +-
src/orca/scripts/apps/notification-daemon.py | 4 +-
src/orca/scripts/apps/packagemanager/Makefile.am | 13 +-
src/orca/scripts/apps/packagemanager/script.py | 118 +-
.../apps/packagemanager/script_utilities.py | 135 +
.../apps/packagemanager/speech_generator.py | 6 +-
src/orca/scripts/apps/pidgin/Makefile.am | 1 +
src/orca/scripts/apps/pidgin/script.py | 143 +-
src/orca/scripts/apps/pidgin/script_utilities.py | 181 +
src/orca/scripts/apps/planner/braille_generator.py | 2 +-
src/orca/scripts/apps/planner/speech_generator.py | 2 +-
src/orca/scripts/apps/soffice/Makefile.am | 1 +
src/orca/scripts/apps/soffice/braille_generator.py | 27 +-
src/orca/scripts/apps/soffice/script.py | 427 +-
src/orca/scripts/apps/soffice/script_utilities.py | 267 +
src/orca/scripts/apps/soffice/speech_generator.py | 46 +-
.../scripts/apps/soffice/structural_navigation.py | 2 +-
src/orca/scripts/apps/yelp/Makefile.am | 3 +-
src/orca/scripts/apps/yelp/script.py | 79 +-
src/orca/scripts/apps/yelp/script_utilities.py | 118 +
src/orca/scripts/toolkits/Gecko/Makefile.am | 1 +
src/orca/scripts/toolkits/Gecko/__init__.py | 1 +
src/orca/scripts/toolkits/Gecko/bookmarks.py | 8 +-
.../scripts/toolkits/Gecko/braille_generator.py | 16 +-
src/orca/scripts/toolkits/Gecko/script.py | 769 +--
.../scripts/toolkits/Gecko/script_utilities.py | 457 ++
.../scripts/toolkits/Gecko/speech_generator.py | 43 +-
.../toolkits/Gecko/structural_navigation.py | 2 +-
.../toolkits/J2SE-access-bridge/Makefile.am | 1 +
.../scripts/toolkits/J2SE-access-bridge/script.py | 114 +-
.../J2SE-access-bridge/script_utilities.py | 151 +
.../J2SE-access-bridge/speech_generator.py | 13 +-
src/orca/speech.py | 2 +-
src/orca/speech_generator.py | 105 +-
src/orca/speechserver.py | 4 +-
src/orca/structural_navigation.py | 52 +-
src/orca/tutorialgenerator.py | 12 +-
src/orca/where_am_I.py | 15 +-
test/harness/orca-customizations.py.in | 1 +
94 files changed, 8550 insertions(+), 7605 deletions(-)
---
diff --git a/configure.in b/configure.in
index 60c9ccc..5e81ef9 100644
--- a/configure.in
+++ b/configure.in
@@ -76,6 +76,7 @@ src/orca/Makefile
src/orca/scripts/Makefile
src/orca/scripts/apps/Makefile
src/orca/scripts/apps/soffice/Makefile
+src/orca/scripts/apps/ddu/Makefile
src/orca/scripts/apps/empathy/Makefile
src/orca/scripts/apps/evolution/Makefile
src/orca/scripts/apps/gajim/Makefile
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e0a3fe2..13ab048 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -34,6 +34,7 @@ src/orca/orca.py
[type: gettext/glade]src/orca/orca-setup.ui
src/orca/phonnames.py
src/orca/rolenames.py
+src/orca/script_utilities.py
src/orca/scripts/apps/acroread.py
src/orca/scripts/apps/evolution/script.py
src/orca/scripts/apps/evolution/speech_generator.py
diff --git a/src/orca/Makefile.am b/src/orca/Makefile.am
index c1b38a9..3680632 100644
--- a/src/orca/Makefile.am
+++ b/src/orca/Makefile.am
@@ -56,6 +56,7 @@ orca_python_PYTHON = \
punctuation_settings.py \
rolenames.py \
script.py \
+ script_utilities.py \
settings.py \
sound.py \
speech.py \
diff --git a/src/orca/bookmarks.py b/src/orca/bookmarks.py
index 2f6bcd1..af093a5 100644
--- a/src/orca/bookmarks.py
+++ b/src/orca/bookmarks.py
@@ -96,14 +96,14 @@ class Bookmarks:
cur_obj = orca_state.locusOfFocus
# Are they the same object?
- if self._script.isSameObject(cur_obj, obj):
+ if self._script.utilities.isSameObject(cur_obj, obj):
# Translators: this announces that the current object is the same
# object pointed to by the bookmark.
#
speech.speak(_('bookmark is current object'))
return
# Are their parents the same?
- elif self._script.isSameObject(cur_obj.parent, obj.parent):
+ elif self._script.utilities.isSameObject(cur_obj.parent, obj.parent):
# Translators: this announces that the current object's parent and
# the parent of the object pointed to by the bookmark are the same.
#
diff --git a/src/orca/braille.py b/src/orca/braille.py
index 6b8fbf9..cf15ee9 100644
--- a/src/orca/braille.py
+++ b/src/orca/braille.py
@@ -699,7 +699,8 @@ class Text(Region):
if caretOffset < 0:
return
- orca_state.activeScript.setCaretOffset(self.accessible, caretOffset)
+ orca_state.activeScript.utilities.setCaretOffset(
+ self.accessible, caretOffset)
def getAttributeMask(self, getLinkMask=True):
"""Creates a string which can be used as the attrOr field of brltty's
@@ -748,14 +749,14 @@ class Text(Region):
n += 1
if attrIndicator:
- enabledAttributes = script.attributeStringToDictionary(
+ keys, enabledAttributes = script.utilities.stringToKeysAndDict(
settings.enabledBrailledTextAttributes)
offset = self.lineOffset
while offset < lineEndOffset:
attributes, startOffset, endOffset = \
- script.getTextAttributes(self.accessible,
- offset, True)
+ script.utilities.textAttributes(self.accessible,
+ offset, True)
if endOffset <= offset:
break
mask = settings.TEXT_ATTR_BRAILLE_NONE
@@ -772,7 +773,7 @@ class Text(Region):
regionMask[i] |= attrIndicator
if selIndicator:
- selections = script.getAllTextSelections(self.accessible)
+ selections = script.utilities.allTextSelections(self.accessible)
for startOffset, endOffset in selections:
maskStart = max(startOffset - self.lineOffset, 0)
maskEnd = min(endOffset - self.lineOffset, stringLength)
@@ -879,7 +880,8 @@ class ReviewText(Region):
been scrolled off the display."""
caretOffset = self.getCaretOffset(offset)
- orca_state.activeScript.setCaretOffset(self.accessible, caretOffset)
+ orca_state.activeScript.utilities.setCaretOffset(
+ self.accessible, caretOffset)
class Line:
"""A horizontal line on the display. Each Line is composed of a sequential
diff --git a/src/orca/braille_generator.py b/src/orca/braille_generator.py
index 27ff747..2ab4eca 100644
--- a/src/orca/braille_generator.py
+++ b/src/orca/braille_generator.py
@@ -139,7 +139,8 @@ class BrailleGenerator(generator.Generator):
or an empty array if no accelerator can be found.
"""
result = []
- [mnemonic, shortcut, accelerator] = self._script.getKeyBinding(obj)
+ [mnemonic, shortcut, accelerator] = \
+ self._script.utilities.mnemonicShortcutAccelerator(obj)
if accelerator:
result.append("(" + accelerator + ")")
return result
@@ -157,7 +158,7 @@ class BrailleGenerator(generator.Generator):
"""
result = []
alertAndDialogCount = \
- self._script.getUnfocusedAlertAndDialogCount(obj)
+ self._script.utilities.unfocusedAlertAndDialogCount(obj)
if alertAndDialogCount > 0:
# Translators: this tells the user how many unfocused
# alert and dialog windows plus the total number of
@@ -205,11 +206,11 @@ class BrailleGenerator(generator.Generator):
# as bugzilla bug 319751.]]]
#
role = parent.getRole()
- if (role != pyatspi.ROLE_FILLER) \
- and (role != pyatspi.ROLE_SECTION) \
- and (role != pyatspi.ROLE_SPLIT_PANE) \
- and (role != pyatspi.ROLE_DESKTOP_FRAME) \
- and (not self._script.isLayoutOnly(parent)):
+ if role != pyatspi.ROLE_FILLER \
+ and role != pyatspi.ROLE_SECTION \
+ and role != pyatspi.ROLE_SPLIT_PANE \
+ and role != pyatspi.ROLE_DESKTOP_FRAME \
+ and not self._script.utilities.isLayoutOnly(parent):
args['role'] = role
parentResult = self.generate(parent, **args)
# [[[TODO: HACK - we've discovered oddness in hierarchies
@@ -222,7 +223,7 @@ class BrailleGenerator(generator.Generator):
#
if parent.getRole() in [pyatspi.ROLE_FILLER,
pyatspi.ROLE_PANEL]:
- label = self._script.getDisplayedLabel(parent)
+ label = self._script.utilities.displayedLabel(parent)
if label and len(label) and not label.isspace():
if not excludeRadioButtonGroup:
args['role'] = parent.getRole()
@@ -299,7 +300,7 @@ class BrailleGenerator(generator.Generator):
text = obj.queryText()
except NotImplementedError:
text = None
- if text and (self._script.isTextArea(obj) \
+ if text and (self._script.utilities.isTextArea(obj) \
or (obj.getRole() in [pyatspi.ROLE_LABEL])):
[lineString, startOffset, endOffset] = text.getTextAtOffset(
text.caretOffset,
@@ -309,8 +310,8 @@ class BrailleGenerator(generator.Generator):
for relation in obj.getRelationSet():
if relation.getRelationType() \
== pyatspi.RELATION_FLOWS_FROM:
- include = \
- not self._script.isTextArea(relation.getTarget(0))
+ include = not self._script.utilities.\
+ isTextArea(relation.getTarget(0))
return include
#####################################################################
@@ -352,7 +353,6 @@ class BrailleGenerator(generator.Generator):
prior = None
else:
prior = self.asString(element)
- combined = self._script.appendString(combined,
- prior,
- delimiter)
+ combined = self._script.utilities.appendString(
+ combined, prior, delimiter)
return combined
diff --git a/src/orca/chat.py b/src/orca/chat.py
index 0964bc7..3abe9e5 100644
--- a/src/orca/chat.py
+++ b/src/orca/chat.py
@@ -655,7 +655,7 @@ class Chat:
text = ""
if settings.chatSpeakRoomName and chatRoomName:
text = _("Message from chat room %s") % chatRoomName
- text = self._script.appendString(text, message)
+ text = self._script.utilities.appendString(text, message)
if len(text.strip()):
speech.speak(text)
@@ -816,7 +816,7 @@ class Chat:
if obj:
for roleList in self._buddyListAncestries:
- if self._script.isDesiredFocusedItem(obj, roleList):
+ if self._script.utilities.hasMatchingHierarchy(obj, roleList):
return True
return False
@@ -835,7 +835,7 @@ class Chat:
for roleList in self._buddyListAncestries:
buddyListRole = roleList[0]
- candidate = self._script.getAncestor( \
+ candidate = self._script.utilities.ancestorWithRole(
obj, [buddyListRole], [pyatspi.ROLE_FRAME])
if self.isBuddyList(candidate):
return True
@@ -882,8 +882,8 @@ class Chat:
if name:
if name == conversation.name:
return conversation
- # Doing an equality check seems to be preferable here
- # to isSameObject as a result of false positives.
+ # Doing an equality check seems to be preferable here to
+ # utilities.isSameObject as a result of false positives.
#
elif obj == conversation.accHistory:
return conversation
@@ -917,7 +917,7 @@ class Chat:
"""
if obj and obj.getState().contains(pyatspi.STATE_SHOWING):
- topLevel = self._script.getTopLevel(obj)
+ topLevel = self._script.utilities.topLevelObject(obj)
if topLevel and topLevel.getState().contains(pyatspi.STATE_ACTIVE):
return True
@@ -937,13 +937,13 @@ class Chat:
# that, we'll look at the frame name. Failing that, scripts
# should override this method. :-)
#
- ancestor = self._script.getAncestor(obj,
- [pyatspi.ROLE_PAGE_TAB,
- pyatspi.ROLE_FRAME],
- [pyatspi.ROLE_APPLICATION])
+ ancestor = self._script.utilities.ancestorWithRole(
+ obj,
+ [pyatspi.ROLE_PAGE_TAB, pyatspi.ROLE_FRAME],
+ [pyatspi.ROLE_APPLICATION])
name = ""
try:
- text = self._script.getDisplayedText(ancestor)
+ text = self._script.utilities.displayedText(ancestor)
if text.lower().strip() != self._script.name.lower().strip():
name = text
except:
@@ -954,11 +954,10 @@ class Chat:
# the item. Therefore, we'll give it one more shot.
#
if not name:
- ancestor = self._script.getAncestor(ancestor,
- [pyatspi.ROLE_FRAME],
- [pyatspi.ROLE_APPLICATION])
+ ancestor = self._script.utilities.ancestorWithRole(
+ ancestor, [pyatspi.ROLE_FRAME], [pyatspi.ROLE_APPLICATION])
try:
- text = self._script.getDisplayedText(ancestor)
+ text = self._script.utilities.displayedText(ancestor)
if text.lower().strip() != self._script.name.lower().strip():
name = text
except:
diff --git a/src/orca/default.py b/src/orca/default.py
index 965676f..afa7614 100644
--- a/src/orca/default.py
+++ b/src/orca/default.py
@@ -31,14 +31,10 @@ __copyright__ = "Copyright (c) 2004-2009 Sun Microsystems Inc." \
__license__ = "LGPL"
import locale
-import math
-import re
-import sys
import time
import pyatspi
import braille
-import chnames
import debug
import eventsynthesizer
import find
@@ -54,8 +50,6 @@ import orca
import orca_prefs
import orca_state
import phonnames
-import pronunciation_dict
-import punctuation_settings
import script
import settings
import speech
@@ -77,7 +71,9 @@ class Script(script.Script):
EMBEDDED_OBJECT_CHARACTER = u'\ufffc'
NO_BREAK_SPACE_CHARACTER = u'\u00a0'
- WORDS_RE = re.compile("(\W+)", re.UNICODE)
+
+ # generatorCache
+ #
DISPLAYED_LABEL = 'displayedLabel'
DISPLAYED_TEXT = 'displayedText'
KEY_BINDING = 'keyBinding'
@@ -2117,835 +2113,6 @@ class Script(script.Script):
return script.Script.processKeyboardEvent(self, keyboardEvent)
- def __sayAllProgressCallback(self, context, progressType):
- # [[[TODO: WDW - this needs work. Need to be able to manage
- # the monitoring of progress and couple that with both updating
- # the visual progress of what is being spoken as well as
- # positioning the cursor when speech has stopped.]]]
- #
- text = context.obj.queryText()
- if progressType == speechserver.SayAllContext.PROGRESS:
- #print "PROGRESS", context.utterance, context.currentOffset
- #obj = context.obj
- #[x, y, width, height] = obj.text.getCharacterExtents(
- # context.currentOffset, 0)
- #print context.currentOffset, x, y, width, height
- #self.drawOutline(x, y, width, height)
- return
- elif progressType == speechserver.SayAllContext.INTERRUPTED:
- #print "INTERRUPTED", context.utterance, context.currentOffset
- text.setCaretOffset(context.currentOffset)
- elif progressType == speechserver.SayAllContext.COMPLETED:
- #print "COMPLETED", context.utterance, context.currentOffset
- orca.setLocusOfFocus(
- None, context.obj, notifyPresentationManager=False)
- text.setCaretOffset(context.currentOffset)
-
- # If there is a selection, clear it. See bug #489504 for more details.
- #
- if text.getNSelections():
- text.setSelection(0, context.currentOffset, context.currentOffset)
-
- def sayAll(self, inputEvent):
- clickCount = self.getClickCount()
- doubleClick = clickCount == 2
- self.lastSayAllEvent = inputEvent
-
- if doubleClick:
- # Try to "say all" for the current dialog/window by flat
- # reviewing everything. See bug #354462 for more details.
- #
- context = self.getFlatReviewContext()
-
- utterances = []
- context.goBegin()
- while True:
- [wordString, x, y, width, height] = \
- context.getCurrent(flat_review.Context.ZONE)
-
- utterances.append(wordString)
-
- moved = context.goNext(flat_review.Context.ZONE,
- flat_review.Context.WRAP_LINE)
-
- if not moved:
- break
-
- speech.speak(utterances)
-
- elif self.isTextArea(orca_state.locusOfFocus):
- try:
- orca_state.locusOfFocus.queryText()
- except NotImplementedError:
- utterances = self.speechGenerator.generateSpeech(
- orca_state.locusOfFocus)
- utterances.extend(self.tutorialGenerator.getTutorial(
- orca_state.locusOfFocus, False))
- speech.speak(utterances)
- except AttributeError:
- pass
- else:
- speech.sayAll(self.textLines(orca_state.locusOfFocus),
- self.__sayAllProgressCallback)
-
- return True
-
- def isTextArea(self, obj):
- """Returns True if obj is a GUI component that is for entering text.
-
- Arguments:
- - obj: an accessible
- """
- return obj and \
- obj.getRole() in (pyatspi.ROLE_TEXT,
- pyatspi.ROLE_PARAGRAPH,
- pyatspi.ROLE_TERMINAL)
-
- def isReadOnlyTextArea(self, obj):
- """Returns True if obj is a text entry area that is read only."""
- state = obj.getState()
- readOnly = self.isTextArea(obj) \
- and state.contains(pyatspi.STATE_FOCUSABLE) \
- and not state.contains(pyatspi.STATE_EDITABLE)
- debug.println(debug.LEVEL_ALL,
- "default.py:isReadOnlyTextArea=%s for %s" \
- % (readOnly, debug.getAccessibleDetails(obj)))
- return readOnly
-
- def getText(self, obj, startOffset, endOffset):
- """Returns the substring of the given object's text specialization.
-
- Arguments:
- - obj: an accessible supporting the accessible text specialization
- - startOffset: the starting character position
- - endOffset: the ending character position
- """
- return obj.queryText().getText(startOffset, endOffset)
-
- def sayPhrase(self, obj, startOffset, endOffset):
- """Speaks the text of an Accessible object between the start and
- end offsets, unless the phrase is empty in which case it's ignored.
-
- Arguments:
- - obj: an Accessible object that implements the AccessibleText
- interface
- - startOffset: the start text offset.
- - endOffset: the end text offset.
- """
-
- phrase = self.getText(obj, startOffset, endOffset)
-
- if len(phrase) and phrase != "\n":
- if phrase.decode("UTF-8").isupper():
- voice = self.voices[settings.UPPERCASE_VOICE]
- else:
- voice = self.voices[settings.DEFAULT_VOICE]
-
- phrase = self.adjustForRepeats(phrase)
- speech.speak(phrase, voice)
- else:
- # Speak blank line if appropriate.
- #
- self.sayCharacter(obj)
-
- def sayLine(self, obj):
- """Speaks the line of an AccessibleText object that contains the
- caret, unless the line is empty in which case it's ignored.
-
- Arguments:
- - obj: an Accessible object that implements the AccessibleText
- interface
- """
-
- # Get the AccessibleText interface of the provided object
- #
- [line, caretOffset, startOffset] = self.getTextLineAtCaret(obj)
- debug.println(debug.LEVEL_FINEST, \
- "sayLine: line=<%s>, len=%d, start=%d, " % \
- (line, len(line), startOffset))
- debug.println(debug.LEVEL_FINEST, \
- "caret=%d, speakBlankLines=%s" % \
- (caretOffset, settings.speakBlankLines))
-
- if len(line) and line != "\n":
- if line.decode("UTF-8").isupper():
- voice = self.voices[settings.UPPERCASE_VOICE]
- else:
- voice = self.voices[settings.DEFAULT_VOICE]
-
- if settings.enableSpeechIndentation:
- self.speakTextIndentation(obj, line)
- line = self.adjustForLinks(obj, line, startOffset)
- line = self.adjustForRepeats(line)
- speech.speak(line, voice)
- else:
- # Speak blank line if appropriate.
- #
- self.sayCharacter(obj)
-
- def sayWord(self, obj):
- """Speaks the word at the caret. [[[TODO: WDW - what if there is no
- word at the caret?]]]
-
- Arguments:
- - obj: an Accessible object that implements the AccessibleText
- interface
- """
-
- text = obj.queryText()
- offset = text.caretOffset
- lastKey = orca_state.lastNonModifierKeyEvent.event_string
- lastWord = orca_state.lastWord
-
- [word, startOffset, endOffset] = \
- text.getTextAtOffset(offset,
- pyatspi.TEXT_BOUNDARY_WORD_START)
-
- # Speak a newline if a control-right-arrow or control-left-arrow
- # was used to cross a line boundary. Handling is different for
- # the two keys since control-right-arrow places the cursor after
- # the last character in a word, but control-left-arrow places
- # the cursor at the beginning of a word.
- #
- if lastKey == "Right" and len(lastWord) > 0:
- lastChar = lastWord[len(lastWord) - 1]
- if lastChar == "\n" and lastWord != word:
- voice = self.voices[settings.DEFAULT_VOICE]
- speech.speakCharacter("\n", voice)
-
- if lastKey == "Left" and len(word) > 0:
- lastChar = word[len(word) - 1]
- if lastChar == "\n" and lastWord != word:
- voice = self.voices[settings.DEFAULT_VOICE]
- speech.speakCharacter("\n", voice)
-
- if self.getLinkIndex(obj, offset) >= 0:
- voice = self.voices[settings.HYPERLINK_VOICE]
- elif word.decode("UTF-8").isupper():
- voice = self.voices[settings.UPPERCASE_VOICE]
- else:
- voice = self.voices[settings.DEFAULT_VOICE]
-
- self.speakMisspelledIndicator(obj, startOffset)
-
- word = self.adjustForRepeats(word)
- orca_state.lastWord = word
- speech.speak(word, voice)
-
- def speakTextIndentation(self, obj, line):
- """Speaks a summary of the number of spaces and/or tabs at the
- beginning of the given line.
-
- Arguments:
- - obj: the text object.
- - line: the string to check for spaces and tabs.
- """
-
- # For the purpose of speaking the text indentation, replace
- # occurances of UTF-8 '\302\240' (non breaking space) with
- # spaces.
- #
- line = line.replace("\302\240", " ")
- line = line.decode("UTF-8")
-
- spaceCount = 0
- tabCount = 0
- utterance = ""
- offset = 0
- while True:
- while (offset < len(line)) and line[offset] == ' ':
- spaceCount += 1
- offset += 1
- if spaceCount:
- # Translators: this is the number of space characters on a line
- # of text.
- #
- utterance += ngettext("%d space",
- "%d spaces",
- spaceCount) % spaceCount + " "
-
- while (offset < len(line)) and line[offset] == '\t':
- tabCount += 1
- offset += 1
- if tabCount:
- # Translators: this is the number of tab characters on a line
- # of text.
- #
- utterance += ngettext("%d tab",
- "%d tabs",
- tabCount) % tabCount + " "
-
- if not (spaceCount or tabCount):
- break
- spaceCount = tabCount = 0
-
- if len(utterance):
- speech.speak(utterance)
-
- def echoPreviousSentence(self, obj):
- """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
- # we hit another sentence delimiter.
- #
- sentenceEndOffset = text.caretOffset - 2
- sentenceStartOffset = sentenceEndOffset
-
- while sentenceStartOffset >= 0:
- [currentChar, startOffset, endOffset] = \
- text.getTextAtOffset(sentenceStartOffset,
- pyatspi.TEXT_BOUNDARY_CHAR)
- [previousChar, startOffset, endOffset] = \
- text.getTextAtOffset(sentenceStartOffset-1,
- pyatspi.TEXT_BOUNDARY_CHAR)
- if self.isSentenceDelimiter(currentChar, previousChar):
- break
- else:
- sentenceStartOffset -= 1
-
- # If we came across a sentence delimiter before hitting any
- # text, we really don't have a previous sentence.
- #
- # Otherwise, get the sentence. Remember we stopped when we
- # hit a sentence delimiter, so the sentence really starts at
- # sentenceStartOffset + 1. getText also does not include
- # the character at sentenceEndOffset, so we need to adjust
- # for that, too.
- #
- if sentenceStartOffset == sentenceEndOffset:
- return
- else:
- sentence = self.getText(obj, sentenceStartOffset + 1,
- sentenceEndOffset + 1)
-
- if self.getLinkIndex(obj, sentenceStartOffset + 1) >= 0:
- voice = self.voices[settings.HYPERLINK_VOICE]
- elif sentence.decode("UTF-8").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
- delimiter between the caret and the end of the word.
-
- The entry condition for this method is that the character
- prior to the current caret position is a word 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.
- - offset: if not None, the offset within the text to use as the
- end of the word.
- """
-
- try:
- text = obj.queryText()
- except NotImplementedError:
- return
-
- if not offset:
- offset = text.caretOffset - 1
- if (offset < 0):
- return
-
- [char, startOffset, endOffset] = \
- text.getTextAtOffset( \
- offset,
- pyatspi.TEXT_BOUNDARY_CHAR)
- if not self.isWordDelimiter(char):
- return
-
- # OK - we seem to be cool so far. So...starting with what
- # should be the last character in the word (caretOffset - 2),
- # work our way to the beginning of the word, stopping when
- # we hit another word delimiter.
- #
- wordEndOffset = offset - 1
- wordStartOffset = wordEndOffset
-
- while wordStartOffset >= 0:
- [char, startOffset, endOffset] = \
- text.getTextAtOffset( \
- wordStartOffset,
- pyatspi.TEXT_BOUNDARY_CHAR)
- if self.isWordDelimiter(char):
- break
- else:
- wordStartOffset -= 1
-
- # If we came across a word delimiter before hitting any
- # text, we really don't have a previous word.
- #
- # Otherwise, get the word. Remember we stopped when we
- # hit a word delimiter, so the word really starts at
- # wordStartOffset + 1. getText also does not include
- # the character at wordEndOffset, so we need to adjust
- # for that, too.
- #
- if wordStartOffset == wordEndOffset:
- return
- else:
- word = self.getText(obj, wordStartOffset + 1, wordEndOffset + 1)
-
- if self.getLinkIndex(obj, wordStartOffset + 1) >= 0:
- voice = self.voices[settings.HYPERLINK_VOICE]
- elif word.decode("UTF-8").isupper():
- voice = self.voices[settings.UPPERCASE_VOICE]
- else:
- voice = self.voices[settings.DEFAULT_VOICE]
-
- word = self.adjustForRepeats(word)
- speech.speak(word, voice)
-
- def sayCharacter(self, obj):
- """Speak the character at the caret.
-
- Arguments:
- - obj: an Accessible object that implements the AccessibleText
- interface
- """
-
- text = obj.queryText()
- offset = text.caretOffset
-
- # If we have selected text and the last event was a move to the
- # right, then speak the character to the left of where the text
- # caret is (i.e. the selected character).
- #
- try:
- mods = orca_state.lastInputEvent.modifiers
- eventString = orca_state.lastInputEvent.event_string
- except:
- mods = 0
- eventString = ""
-
- if (mods & settings.SHIFT_MODIFIER_MASK) \
- and eventString in ["Right", "Down"]:
- offset -= 1
-
- character, startOffset, endOffset = \
- text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR)
- if not character:
- character = "\n"
-
- if self.getLinkIndex(obj, offset) >= 0:
- voice = self.voices[settings.HYPERLINK_VOICE]
- elif character.decode("UTF-8").isupper():
- voice = self.voices[settings.UPPERCASE_VOICE]
- else:
- voice = self.voices[settings.DEFAULT_VOICE]
-
- debug.println(debug.LEVEL_FINEST, \
- "sayCharacter: char=<%s>, startOffset=%d, " % \
- (character, startOffset))
- debug.println(debug.LEVEL_FINEST, \
- "caretOffset=%d, endOffset=%d, speakBlankLines=%s" % \
- (offset, endOffset, settings.speakBlankLines))
-
- if character == "\n":
- line = text.getTextAtOffset(max(0, offset),
- pyatspi.TEXT_BOUNDARY_LINE_START)
- if not line[0] or line[0] == "\n":
- # This is a blank line. Announce it if the user requested
- # that blank lines be spoken.
- if settings.speakBlankLines:
- # Translators: "blank" is a short word to mean the
- # user has navigated to an empty line.
- #
- speech.speak(_("blank"), voice, False)
- return
-
- self.speakMisspelledIndicator(obj, offset)
- speech.speakCharacter(character, voice)
-
- def isFunctionalDialog(self, obj):
- """Returns true if the window is a functioning as a dialog.
- This method should be subclassed by application scripts as needed.
- """
-
- return False
-
- def getUnfocusedAlertAndDialogCount(self, obj):
- """If the current application has one or more alert or dialog
- windows and the currently focused window is not an alert or a dialog,
- return a count of the number of alert and dialog windows, otherwise
- return a count of zero.
-
- Arguments:
- - obj: the Accessible object
-
- Returns the alert and dialog count.
- """
-
- alertAndDialogCount = 0
- app = obj.getApplication()
- window = self.getTopLevel(obj)
- if window and window.getRole() != pyatspi.ROLE_ALERT and \
- window.getRole() != pyatspi.ROLE_DIALOG and \
- not self.isFunctionalDialog(window):
- for child in app:
- if child.getRole() == pyatspi.ROLE_ALERT or \
- child.getRole() == pyatspi.ROLE_DIALOG or \
- self.isFunctionalDialog(child):
- alertAndDialogCount += 1
-
- return alertAndDialogCount
-
- def presentToolTip(self, obj):
- """
- Speaks the tooltip for the current object of interest.
- """
-
- # The tooltip is generally the accessible description. If
- # the description is not set, present the text that is
- # spoken when the object receives keyboard focus.
- #
- text = ""
- if obj.description:
- speechResult = brailleResult = obj.description
- else:
- # [[[TODO: WDW to JD: I see what you mean about making the
- # _getLabelAndName method a public method. We might consider
- # doing that when we get to the braille refactor where we
- # will probably make an uber generator class/module that the
- # speech and braille generator classes can subclass. For
- # now, I've kind of hacked at the speech result. The idea
- # is that the speech result might return an audio cue and
- # voice specification. Of course, if it does that, then
- # the speechResult[0] assumption will fail. :-(]]]
- #
- speechResult = self.whereAmI.getWhereAmI(obj, True)
- brailleResult = speechResult[0]
- debug.println(debug.LEVEL_FINEST,
- "presentToolTip: text='%s'" % speechResult)
- if speechResult:
- speech.speak(speechResult)
- if brailleResult:
- self.displayBrailleMessage(brailleResult)
-
- def doWhereAmI(self, inputEvent, basicOnly):
- """Peforms the whereAmI operation.
-
- Arguments:
- - inputEvent: The original inputEvent
- """
-
- obj = orca_state.locusOfFocus
- self.updateBraille(obj)
-
- return self.whereAmI.whereAmI(obj, basicOnly)
-
- def whereAmIBasic(self, inputEvent):
- """Speaks basic information about the current object of interest.
- """
-
- self.doWhereAmI(inputEvent, True)
-
- def whereAmIDetailed(self, inputEvent):
- """Speaks detailed/custom information about the current object of
- interest.
- """
-
- self.doWhereAmI(inputEvent, False)
-
- def presentTitle(self, inputEvent):
- """Speaks and brailles the title of the window with focus.
- """
-
- obj = orca_state.locusOfFocus
- self.updateBraille(obj)
- speech.speak(self.speechGenerator.generateTitle(obj))
-
- def presentStatusBar(self, inputEvent):
- """Speaks and brailles the contents of the status bar and/or default
- button of the window with focus.
- """
-
- obj = orca_state.locusOfFocus
- self.updateBraille(obj)
-
- frame, dialog = self.findFrameAndDialog(obj)
- if frame:
- # In windows with lots of objects (Thunderbird, Firefox, etc.)
- # If we wait until we've checked for both the status bar and
- # a default button, there may be a noticable delay. Therefore,
- # speak the status bar info immediately and then go looking
- # for a default button.
- #
- speech.speak(self.speechGenerator.generateStatusBar(frame))
- window = dialog or frame
- if window:
- speech.speak(self.speechGenerator.generateDefaultButton(window))
-
- def findStatusBar(self, obj):
- """Returns the status bar in the window which contains obj.
- """
-
- # There are some objects which are not worth descending.
- #
- skipRoles = [pyatspi.ROLE_TREE,
- pyatspi.ROLE_TREE_TABLE,
- pyatspi.ROLE_TABLE]
-
- if obj.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS) \
- or obj.getRole() in skipRoles:
- return
-
- statusBar = None
- # The status bar is likely near the bottom of the window.
- #
- for i in range(obj.childCount - 1, -1, -1):
- if obj[i].getRole() == pyatspi.ROLE_STATUS_BAR:
- statusBar = obj[i]
- elif not obj[i] in skipRoles:
- statusBar = self.findStatusBar(obj[i])
-
- if statusBar:
- break
-
- return statusBar
-
- def findDefaultButton(self, obj):
- """Returns the default button in dialog, obj.
- """
-
- # There are some objects which are not worth descending.
- #
- skipRoles = [pyatspi.ROLE_TREE,
- pyatspi.ROLE_TREE_TABLE,
- pyatspi.ROLE_TABLE]
-
- if obj.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS) \
- or obj.getRole() in skipRoles:
- return
-
- defaultButton = None
- # The default button is likely near the bottom of the window.
- #
- for i in range(obj.childCount - 1, -1, -1):
- if obj[i].getRole() == pyatspi.ROLE_PUSH_BUTTON \
- and obj[i].getState().contains(pyatspi.STATE_IS_DEFAULT):
- defaultButton = obj[i]
- elif not obj[i].getRole() in skipRoles:
- defaultButton = self.findDefaultButton(obj[i])
-
- if defaultButton:
- break
-
- return defaultButton
-
- def findFrameAndDialog(self, obj):
- """Returns the frame and (possibly) the dialog containing
- the object.
- """
-
- results = [None, None]
-
- parent = obj.parent
- while parent and (parent.parent != parent):
- if parent.getRole() == pyatspi.ROLE_FRAME:
- results[0] = parent
- if parent.getRole() in [pyatspi.ROLE_DIALOG,
- pyatspi.ROLE_FILE_CHOOSER]:
- results[1] = parent
- parent = parent.parent
-
- return results
-
- def findCommonAncestor(self, a, b):
- """Finds the common ancestor between Accessible a and Accessible b.
-
- Arguments:
- - a: Accessible
- - b: Accessible
- """
-
- debug.println(debug.LEVEL_FINEST,
- "default.findCommonAncestor...")
-
- if (not a) or (not b):
- return None
-
- if a == b:
- return a
-
- aParents = [a]
- try:
- parent = a.parent
- while parent and (parent.parent != parent):
- aParents.append(parent)
- parent = parent.parent
- aParents.reverse()
- except:
- debug.printException(debug.LEVEL_FINEST)
-
- bParents = [b]
- try:
- parent = b.parent
- while parent and (parent.parent != parent):
- bParents.append(parent)
- parent = parent.parent
- bParents.reverse()
- except:
- debug.printException(debug.LEVEL_FINEST)
-
- commonAncestor = None
-
- maxSearch = min(len(aParents), len(bParents))
- i = 0
- while i < maxSearch:
- if self.isSameObject(aParents[i], bParents[i]):
- commonAncestor = aParents[i]
- i += 1
- else:
- break
-
- debug.println(debug.LEVEL_FINEST,
- "...default.findCommonAncestor")
-
- return commonAncestor
-
- def handleProgressBarUpdate(self, event, obj):
- """Determine whether this progress bar event should be spoken or not.
- It should be spoken if:
- 1/ settings.enableProgressBarUpdates is True.
- 2/ settings.progressBarVerbosity matches the current location of the
- progress bar.
- 3/ The time of this event exceeds the
- settings.progressBarUpdateInterval value. This value
- indicates the time (in seconds) between potential spoken
- progress bar updates.
- 4/ The new value of the progress bar (converted to an integer),
- is different from the last one or equals 100 (i.e complete).
-
- Arguments:
- - event: if not None, the Event that caused this to happen
- - obj: the Accessible progress bar object.
- """
-
- if settings.enableProgressBarUpdates:
- makeAnnouncment = False
- if settings.progressBarVerbosity == settings.PROGRESS_BAR_ALL:
- makeAnnouncement = True
- elif settings.progressBarVerbosity == settings.PROGRESS_BAR_WINDOW:
- makeAnnouncement = self.isSameObject( \
- self.getTopLevel(obj), self.findActiveWindow())
- elif orca_state.locusOfFocus:
- makeAnnouncement = self.isSameObject( \
- obj.getApplication(),
- orca_state.locusOfFocus.getApplication())
-
- if makeAnnouncement:
- currentTime = time.time()
-
- # Check for defunct progress bars. Get rid of them if they
- # are all defunct. Also find out which progress bar was
- # the most recently updated.
- #
- defunctBars = 0
- mostRecentUpdate = [obj, 0]
- for key, value in self.lastProgressBarTime.items():
- if value > mostRecentUpdate[1]:
- mostRecentUpdate = [key, value]
- try:
- isDefunct = \
- key.getState().contains(pyatspi.STATE_DEFUNCT)
- except:
- isDefunct = True
- if isDefunct:
- defunctBars += 1
-
- if defunctBars == len(self.lastProgressBarTime):
- self.lastProgressBarTime = {}
- self.lastProgressBarValue = {}
-
- # If this progress bar is not already known, create initial
- # values for it.
- #
- if obj not in self.lastProgressBarTime:
- self.lastProgressBarTime[obj] = 0.0
- if obj not in self.lastProgressBarValue:
- self.lastProgressBarValue[obj] = None
-
- lastProgressBarTime = self.lastProgressBarTime[obj]
- lastProgressBarValue = self.lastProgressBarValue[obj]
- value = obj.queryValue()
- percentValue = int((value.currentValue / \
- (value.maximumValue - value.minimumValue)) * 100.0)
-
- if (currentTime - lastProgressBarTime) > \
- settings.progressBarUpdateInterval \
- or percentValue == 100:
- if lastProgressBarValue != percentValue:
- utterances = []
-
- # There may be cases when more than one progress
- # bar is updating at the same time in a window.
- # If this is the case, then speak the index of this
- # progress bar in the dictionary of known progress
- # bars, as well as the value. But only speak the
- # index if this progress bar was not the most
- # recently updated to prevent chattiness.
- #
- if len(self.lastProgressBarTime) > 1:
- index = 0
- for key in self.lastProgressBarTime.keys():
- if key == obj and key != mostRecentUpdate[0]:
- # Translators: this is an index value
- # so that we can tell which progress bar
- # we are referring to.
- #
- label = _("Progress bar %d.") % (index + 1)
- utterances.append(label)
- else:
- index += 1
-
- utterances.extend(self.speechGenerator.generateSpeech(
- obj, alreadyFocused=True))
-
- speech.speak(utterances)
-
- self.lastProgressBarTime[obj] = currentTime
- self.lastProgressBarValue[obj] = percentValue
-
def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
"""Called when the visual object with focus changes.
@@ -2988,7 +2155,7 @@ class Script(script.Script):
# the objects really could be different even though they seem the
# same. Logged as bug 319675.]]]
#
- if self.isSameObject(oldLocusOfFocus, newLocusOfFocus):
+ if self.utilities.isSameObject(oldLocusOfFocus, newLocusOfFocus):
return
# Well...now that we got that behind us, let's do what we're supposed
@@ -3025,16 +2192,16 @@ class Script(script.Script):
# 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.]]]
+ # [[[TODO: WDW - this should be pushed into script_utilities'
+ # adjustForPronunciation method.]]]
#
rolesList = [pyatspi.ROLE_TABLE_CELL, \
pyatspi.ROLE_TABLE, \
pyatspi.ROLE_SCROLL_PANE, \
pyatspi.ROLE_PANEL, \
pyatspi.ROLE_PANEL]
- if self.isDesiredFocusedItem(newLocusOfFocus, rolesList) and \
- newLocusOfFocus.getApplication().name == "orca":
+ if self.utilities.hasMatchingHierarchy(newLocusOfFocus, rolesList) \
+ and newLocusOfFocus.getApplication().name == "orca":
orca_state.usePronunciationDictionary = False
else:
orca_state.usePronunciationDictionary = True
@@ -3071,7 +2238,7 @@ class Script(script.Script):
except:
pass
else:
- index = self.getCellIndex(newLocusOfFocus)
+ index = self.utilities.cellIndex(newLocusOfFocus)
column = table.getColumnAtIndex(index)
self.pointOfReference['lastColumn'] = column
row = table.getRowAtIndex(index)
@@ -3097,64 +2264,12 @@ class Script(script.Script):
self.handleProgressBarUpdate(event, obj)
if self.flatReviewContext:
- if self.isSameObject(
+ if self.utilities.isSameObject(
obj,
self.flatReviewContext.getCurrentAccessible()):
self.updateBrailleReview()
return
- # We care if panels are suddenly showing. The reason for this
- # is that some applications, such as Evolution, will bring up
- # a wizard dialog that uses "Forward" and "Backward" buttons
- # that change the contents of the dialog. We only discover
- # this through showing events. [[[TODO: WDW - perhaps what we
- # really want is to speak unbound labels that are suddenly
- # showing? event.detail == 1 means object is showing.]]]
- #
- # [[[TODO: WDW - I added the 'False' condition to prevent this
- # condition from ever working. I wanted to keep the code around,
- # though, just in case we want to reuse it somewhere else. The
- # bug that spurred all of this on is:
- #
- # http://bugzilla.gnome.org/show_bug.cgi?id=338687
- #
- # The main problem is that the profile editor in gnome-terminal
- # ended up being very verbose and speaking lots of things it
- # should not have been speaking.]]]
- #
- if False and (obj.getRole() == pyatspi.ROLE_PANEL) \
- and (event.detail1 == 1) \
- and self.isInActiveApp(obj):
-
- # It's only showing if its parent is showing. [[[TODO: WDW -
- # HACK we stop at the application level because applications
- # never seem to have their showing state set.]]]
- #
- reallyShowing = True
- parent = obj.parent
- while reallyShowing \
- and parent \
- and (parent != parent.parent) \
- and (parent.getRole() != pyatspi.ROLE_APPLICATION):
- debug.println(debug.LEVEL_FINEST,
- "default.visualAppearanceChanged - " \
- + "checking parent")
- reallyShowing = parent.getState().contains( \
- pyatspi.STATE_SHOWING)
- parent = parent.parent
-
- # Find all the unrelated labels in the dialog and speak them.
- #
- if reallyShowing:
- utterances = []
- labels = self.findUnrelatedLabels(obj)
- for label in labels:
- utterances.append(label.name)
-
- speech.speak(utterances)
-
- return
-
# If this object is CONTROLLED_BY the object that currently
# has focus, speak/braille this object.
#
@@ -3191,7 +2306,7 @@ class Script(script.Script):
speech.speak(utterances)
return
- if not self.isSameObject(obj, orca_state.locusOfFocus):
+ if not self.utilities.isSameObject(obj, orca_state.locusOfFocus):
return
# Radio buttons normally change their state when you arrow to them,
@@ -3265,10 +2380,1189 @@ class Script(script.Script):
########################################################################
# #
+ # INPUT EVENT HANDLERS (AKA ORCA COMMANDS) #
+ # #
+ ########################################################################
+
+ def bypassNextCommand(self, inputEvent=None):
+ """Causes the next keyboard command to be ignored by Orca
+ and passed along to the current application.
+
+ Returns True to indicate the input event has been consumed.
+ """
+
+ # Translators: Orca normally intercepts all keyboard
+ # commands and only passes them along to the current
+ # application when they are not Orca commands. This
+ # command causes the next command issued to be passed
+ # along to the current application, bypassing Orca's
+ # interception of it.
+ #
+ speech.speak(_("Bypass mode enabled."))
+ orca_state.bypassNextCommand = True
+ return True
+
+ def enterLearnMode(self, inputEvent=None):
+ """Turns learn mode on. The user must press the escape key to exit
+ learn mode.
+
+ Returns True to indicate the input event has been consumed.
+ """
+
+ if settings.learnModeEnabled:
+ return True
+
+ speech.speak(
+ # Translators: Orca has a "Learn Mode" that will allow
+ # the user to type any key on the keyboard and hear what
+ # the effects of that key would be. The effects might
+ # be what Orca would do if it had a handler for the
+ # particular key combination, or they might just be to
+ # echo the name of the key if Orca doesn't have a handler.
+ # This text here is what is spoken to the user.
+ #
+ _("Entering learn mode. Press any key to hear its function. " \
+ "To exit learn mode, press the escape key."))
+
+ # Translators: Orca has a "Learn Mode" that will allow
+ # the user to type any key on the keyboard and hear what
+ # the effects of that key would be. The effects might
+ # be what Orca would do if it had a handler for the
+ # particular key combination, or they might just be to
+ # echo the name of the key if Orca doesn't have a handler.
+ # This text here is what is to be presented on the braille
+ # display.
+ #
+ self.displayBrailleMessage(_("Learn mode. Press escape to exit."))
+ settings.learnModeEnabled = True
+ return True
+
+ def findNext(self, inputEvent):
+ """Searches forward for the next instance of the string
+ searched for via the Orca Find dialog. Other than direction
+ and the starting point, the search options initially specified
+ (case sensitivity, window wrap, and full/partial match) are
+ preserved.
+ """
+
+ lastQuery = find.getLastQuery()
+ if lastQuery:
+ lastQuery.searchBackwards = False
+ lastQuery.startAtTop = False
+ self.find(lastQuery)
+ else:
+ orca.showFindGUI()
+
+ def findPrevious(self, inputEvent):
+ """Searches backwards for the next instance of the string
+ searched for via the Orca Find dialog. Other than direction
+ and the starting point, the search options initially specified
+ (case sensitivity, window wrap, and full/or partial match) are
+ preserved.
+ """
+
+ lastQuery = find.getLastQuery()
+ if lastQuery:
+ lastQuery.searchBackwards = True
+ lastQuery.startAtTop = False
+ self.find(lastQuery)
+ else:
+ orca.showFindGUI()
+
+ def addBookmark(self, inputEvent):
+ """ Add an in-page accessible object bookmark for this key.
+ Delegates to Bookmark.addBookmark """
+ bookmarks = self.getBookmarks()
+ bookmarks.addBookmark(inputEvent)
+
+ def bookmarkCurrentWhereAmI(self, inputEvent):
+ """ Report "Where am I" information for this bookmark relative to the
+ current pointer location. Delegates to
+ Bookmark.bookmarkCurrentWhereAmI"""
+ bookmarks = self.getBookmarks()
+ bookmarks.bookmarkCurrentWhereAmI(inputEvent)
+
+ def goToBookmark(self, inputEvent):
+ """ Go to the bookmark indexed by inputEvent.hw_code. Delegates to
+ Bookmark.goToBookmark """
+ bookmarks = self.getBookmarks()
+ bookmarks.goToBookmark(inputEvent)
+
+ def goToNextBookmark(self, inputEvent):
+ """ Go to the next bookmark location. If no bookmark has yet to be
+ selected, the first bookmark will be used. Delegates to
+ Bookmark.goToNextBookmark """
+ bookmarks = self.getBookmarks()
+ bookmarks.goToNextBookmark(inputEvent)
+
+ def goToPrevBookmark(self, inputEvent):
+ """ Go to the previous bookmark location. If no bookmark has yet to
+ be selected, the first bookmark will be used. Delegates to
+ Bookmark.goToPrevBookmark """
+ bookmarks = self.getBookmarks()
+ bookmarks.goToPrevBookmark(inputEvent)
+
+ def saveBookmarks(self, inputEvent):
+ """ Save the bookmarks for this script. Delegates to
+ Bookmark.saveBookmarks """
+ bookmarks = self.getBookmarks()
+ bookmarks.saveBookmarks(inputEvent)
+
+ def panBrailleLeft(self, inputEvent=None, panAmount=0):
+ """Pans the braille display to the left. If panAmount is non-zero,
+ the display is panned by that many cells. If it is 0, the display
+ is panned one full display width. In flat review mode, panning
+ beyond the beginning will take you to the end of the previous line.
+
+ In focus tracking mode, the cursor stays at its logical position.
+ In flat review mode, the review cursor moves to character
+ associated with cell 0."""
+
+ if self.flatReviewContext:
+ if self.isBrailleBeginningShowing():
+ self.flatReviewContext.goBegin(flat_review.Context.LINE)
+ self.reviewPreviousCharacter(inputEvent)
+ else:
+ self.panBrailleInDirection(panAmount, panToLeft=True)
+
+ # This will update our target cursor cell
+ #
+ self._setFlatReviewContextToBeginningOfBrailleDisplay()
+
+ [charString, x, y, width, height] = \
+ self.flatReviewContext.getCurrent(flat_review.Context.CHAR)
+ self.drawOutline(x, y, width, height)
+
+ self.targetCursorCell = 1
+ self.updateBrailleReview(self.targetCursorCell)
+ elif self.isBrailleBeginningShowing() and orca_state.locusOfFocus \
+ and self.utilities.isTextArea(orca_state.locusOfFocus):
+
+ # If we're at the beginning of a line of a multiline text
+ # area, then force it's caret to the end of the previous
+ # line. The assumption here is that we're currently
+ # viewing the line that has the caret -- which is a pretty
+ # good assumption for focus tacking mode. When we set the
+ # caret position, we will get a caret event, which will
+ # then update the braille.
+ #
+ text = orca_state.locusOfFocus.queryText()
+ [lineString, startOffset, endOffset] = text.getTextAtOffset(
+ text.caretOffset,
+ pyatspi.TEXT_BOUNDARY_LINE_START)
+ movedCaret = False
+ if startOffset > 0:
+ movedCaret = text.setCaretOffset(startOffset - 1)
+
+ # If we didn't move the caret and we're in a terminal, we
+ # jump into flat review to review the text. See
+ # http://bugzilla.gnome.org/show_bug.cgi?id=482294.
+ #
+ if (not movedCaret) \
+ and (orca_state.locusOfFocus.getRole() \
+ == pyatspi.ROLE_TERMINAL):
+ context = self.getFlatReviewContext()
+ context.goBegin(flat_review.Context.LINE)
+ self.reviewPreviousCharacter(inputEvent)
+ else:
+ self.panBrailleInDirection(panAmount, panToLeft=True)
+ # We might be panning through a flashed message.
+ #
+ braille.resetFlashTimer()
+ self.refreshBraille(False, stopFlash=False)
+
+ return True
+
+ def panBrailleLeftOneChar(self, inputEvent=None):
+ """Nudges the braille display one character to the left.
+
+ In focus tracking mode, the cursor stays at its logical position.
+ In flat review mode, the review cursor moves to character
+ associated with cell 0."""
+
+ self.panBrailleLeft(inputEvent, 1)
+
+ def panBrailleRight(self, inputEvent=None, panAmount=0):
+ """Pans the braille display to the right. If panAmount is non-zero,
+ the display is panned by that many cells. If it is 0, the display
+ is panned one full display width. In flat review mode, panning
+ beyond the end will take you to the begininng of the next line.
+
+ In focus tracking mode, the cursor stays at its logical position.
+ In flat review mode, the review cursor moves to character
+ associated with cell 0."""
+
+ if self.flatReviewContext:
+ if self.isBrailleEndShowing():
+ self.flatReviewContext.goEnd(flat_review.Context.LINE)
+ self.reviewNextCharacter(inputEvent)
+ else:
+ self.panBrailleInDirection(panAmount, panToLeft=False)
+
+ # This will update our target cursor cell
+ #
+ self._setFlatReviewContextToBeginningOfBrailleDisplay()
+
+ [charString, x, y, width, height] = \
+ self.flatReviewContext.getCurrent(flat_review.Context.CHAR)
+
+ self.drawOutline(x, y, width, height)
+
+ self.targetCursorCell = 1
+ self.updateBrailleReview(self.targetCursorCell)
+ elif self.isBrailleEndShowing() and orca_state.locusOfFocus \
+ and self.utilities.isTextArea(orca_state.locusOfFocus):
+ # If we're at the end of a line of a multiline text area, then
+ # force it's caret to the beginning of the next line. The
+ # assumption here is that we're currently viewing the line that
+ # has the caret -- which is a pretty good assumption for focus
+ # tacking mode. When we set the caret position, we will get a
+ # caret event, which will then update the braille.
+ #
+ text = orca_state.locusOfFocus.queryText()
+ [lineString, startOffset, endOffset] = text.getTextAtOffset(
+ text.caretOffset,
+ pyatspi.TEXT_BOUNDARY_LINE_START)
+ if endOffset < text.characterCount:
+ text.setCaretOffset(endOffset)
+ else:
+ self.panBrailleInDirection(panAmount, panToLeft=False)
+ # We might be panning through a flashed message.
+ #
+ braille.resetFlashTimer()
+ self.refreshBraille(False, stopFlash=False)
+
+ return True
+
+ def panBrailleRightOneChar(self, inputEvent=None):
+ """Nudges the braille display one character to the right.
+
+ In focus tracking mode, the cursor stays at its logical position.
+ In flat review mode, the review cursor moves to character
+ associated with cell 0."""
+
+ self.panBrailleRight(inputEvent, 1)
+
+ def goBrailleHome(self, inputEvent=None):
+ """Returns to the component with focus."""
+
+ if self.flatReviewContext:
+ return self.toggleFlatReviewMode(inputEvent)
+ else:
+ return braille.returnToRegionWithFocus(inputEvent)
+
+ def setContractedBraille(self, inputEvent=None):
+ """Toggles contracted braille."""
+
+ self._setContractedBraille(inputEvent)
+ return True
+
+ def processRoutingKey(self, inputEvent=None):
+ """Processes a cursor routing key."""
+
+ braille.processRoutingKey(inputEvent)
+ return True
+
+ def processBrailleCutBegin(self, inputEvent=None):
+ """Clears the selection and moves the caret offset in the currently
+ active text area.
+ """
+
+ obj, caretOffset = self.getBrailleCaretContext(inputEvent)
+
+ if caretOffset >= 0:
+ self.utilities.clearTextSelection(obj)
+ self.utilities.setCaretOffset(obj, caretOffset)
+
+ return True
+
+ def processBrailleCutLine(self, inputEvent=None):
+ """Extends the text selection in the currently active text
+ area and also copies the selected text to the system clipboard."""
+
+ obj, caretOffset = self.getBrailleCaretContext(inputEvent)
+
+ if caretOffset >= 0:
+ self.utilities.adjustTextSelection(obj, caretOffset)
+ texti = obj.queryText()
+ startOffset, endOffset = texti.getSelection(0)
+ import gtk
+ clipboard = gtk.clipboard_get()
+ clipboard.set_text(texti.getText(startOffset, endOffset))
+
+ return True
+
+ def routePointerToItem(self, inputEvent=None):
+ """Moves the mouse pointer to the current item."""
+
+ # Store the original location for scripts which want to restore
+ # it later.
+ #
+ self.oldMouseCoordinates = self.utilities.absoluteMouseCoordinates()
+ self.lastMouseRoutingTime = time.time()
+ if self.flatReviewContext:
+ self.flatReviewContext.routeToCurrent()
+ else:
+ try:
+ eventsynthesizer.routeToCharacter(orca_state.locusOfFocus)
+ except:
+ try:
+ eventsynthesizer.routeToObject(orca_state.locusOfFocus)
+ except:
+ # Translators: Orca has a command that allows the user
+ # to move the mouse pointer to the current object. If
+ # for some reason Orca cannot identify the current
+ # location, it will speak this message.
+ #
+ speech.speak(_("Could not find current location."))
+
+ return True
+
+ def presentStatusBar(self, inputEvent):
+ """Speaks and brailles the contents of the status bar and/or default
+ button of the window with focus.
+ """
+
+ obj = orca_state.locusOfFocus
+ self.updateBraille(obj)
+
+ frame, dialog = self.utilities.frameAndDialog(obj)
+ if frame:
+ # In windows with lots of objects (Thunderbird, Firefox, etc.)
+ # If we wait until we've checked for both the status bar and
+ # a default button, there may be a noticable delay. Therefore,
+ # speak the status bar info immediately and then go looking
+ # for a default button.
+ #
+ speech.speak(self.speechGenerator.generateStatusBar(frame))
+ window = dialog or frame
+ if window:
+ speech.speak(self.speechGenerator.generateDefaultButton(window))
+
+ def presentTitle(self, inputEvent):
+ """Speaks and brailles the title of the window with focus.
+ """
+
+ obj = orca_state.locusOfFocus
+ self.updateBraille(obj)
+ speech.speak(self.speechGenerator.generateTitle(obj))
+
+ def readCharAttributes(self, inputEvent=None):
+ """Reads the attributes associated with the current text character.
+ Calls outCharAttributes to speak a list of attributes. By default,
+ a certain set of attributes will be spoken. If this is not desired,
+ then individual application scripts should override this method to
+ only speak the subset required.
+ """
+
+ try:
+ text = orca_state.locusOfFocus.queryText()
+ except:
+ pass
+ else:
+ caretOffset = text.caretOffset
+
+ # Creates dictionaries of the default attributes, plus the set
+ # of attributes specific to the character at the caret offset.
+ # Combine these two dictionaries and then extract just the
+ # entries we are interested in.
+ #
+ defAttributes = text.getDefaultAttributes()
+ debug.println(debug.LEVEL_FINEST, \
+ "readCharAttributes: default text attributes: %s" % \
+ defAttributes)
+ [defUser, defDict] = \
+ self.utilities.stringToKeysAndDict(defAttributes)
+ allAttributes = defDict
+
+ charAttributes = text.getAttributes(caretOffset)
+ if charAttributes[0]:
+ [charList, charDict] = \
+ self.utilities.stringToKeysAndDict(charAttributes[0])
+
+ # It looks like some applications like Evolution and Star
+ # Office don't implement getDefaultAttributes(). In that
+ # case, the best we can do is use the specific text
+ # attributes for this character returned by getAttributes().
+ #
+ if allAttributes:
+ for key in charDict.keys():
+ allAttributes[key] = charDict[key]
+ else:
+ allAttributes = charDict
+
+ # Get a dictionary of text attributes that the user cares about.
+ #
+ [userAttrList, userAttrDict] = self.utilities.stringToKeysAndDict(
+ settings.enabledSpokenTextAttributes)
+
+ # Create a dictionary of just the items we are interested in.
+ # Always include size and family-name. For the others, if the
+ # value is the default, then ignore it.
+ #
+ attributes = {}
+ for key in userAttrList:
+ if key in allAttributes:
+ textAttr = allAttributes.get(key)
+ userAttr = userAttrDict.get(key)
+ if textAttr != userAttr or len(userAttr) == 0:
+ attributes[key] = textAttr
+
+ self.outputCharAttributes(userAttrList, attributes)
+
+ # If this is a hypertext link, then let the user know:
+ #
+ if self.utilities.linkIndex(
+ orca_state.locusOfFocus, caretOffset) >= 0:
+ # Translators: this indicates that this piece of
+ # text is a hypertext link.
+ #
+ speech.speak(_("link"))
+
+ return True
+
+ def reportScriptInfo(self, inputEvent=None):
+ """Output useful information on the current script via speech
+ and braille. This information will be helpful to script writers.
+ """
+
+ return self.utilities.scriptInfo()
+
+ def leftClickReviewItem(self, inputEvent=None):
+ """Performs a left mouse button click on the current item."""
+
+ if self.flatReviewContext:
+ self.flatReviewContext.clickCurrent(1)
+ else:
+ try:
+ eventsynthesizer.clickCharacter(orca_state.locusOfFocus, 1)
+ except:
+ try:
+ eventsynthesizer.clickObject(orca_state.locusOfFocus, 1)
+ except:
+ # Translators: Orca has a command that allows the user
+ # to move the mouse pointer to the current object. If
+ # for some reason Orca cannot identify the current
+ # location, it will speak this message.
+ #
+ speech.speak(_("Could not find current location."))
+ return True
+
+ def rightClickReviewItem(self, inputEvent=None):
+ """Performs a right mouse button click on the current item."""
+
+ if self.flatReviewContext:
+ self.flatReviewContext.clickCurrent(3)
+ else:
+ try:
+ eventsynthesizer.clickCharacter(orca_state.locusOfFocus, 3)
+ except:
+ try:
+ eventsynthesizer.clickObject(orca_state.locusOfFocus, 3)
+ except:
+ # Translators: Orca has a command that allows the user
+ # to move the mouse pointer to the current object. If
+ # for some reason Orca cannot identify the current
+ # location, it will speak this message.
+ #
+ speech.speak(_("Could not find current location."))
+ return True
+
+ def spellCurrentItem(self, itemString):
+ """Spell the current flat review word or line.
+
+ Arguments:
+ - itemString: the string to spell.
+ """
+
+ for (charIndex, character) in enumerate(itemString.decode("UTF-8")):
+ if character.isupper():
+ speech.speak(character.encode("UTF-8"),
+ self.voices[settings.UPPERCASE_VOICE])
+ else:
+ speech.speak(character.encode("UTF-8"))
+
+ def _reviewCurrentItem(self, inputEvent, targetCursorCell=0,
+ speechType=1):
+ """Presents the current item to the user.
+
+ Arguments:
+ - inputEvent - the current input event.
+ - targetCursorCell - if non-zero, the target braille cursor cell.
+ - speechType - the desired presentation: speak (1), spell (2), or
+ phonetic (3).
+ """
+
+ context = self.getFlatReviewContext()
+ [wordString, x, y, width, height] = \
+ context.getCurrent(flat_review.Context.WORD)
+ self.drawOutline(x, y, width, height)
+
+ # Don't announce anything from speech if the user used
+ # the Braille display as an input device.
+ #
+ if not isinstance(inputEvent, input_event.BrailleEvent):
+ if (not wordString) \
+ or (not len(wordString)) \
+ or (wordString == "\n"):
+ # Translators: "blank" is a short word to mean the
+ # user has navigated to an empty line.
+ #
+ speech.speak(_("blank"))
+ else:
+ [lineString, x, y, width, height] = \
+ context.getCurrent(flat_review.Context.LINE)
+ if lineString == "\n":
+ # Translators: "blank" is a short word to mean the
+ # user has navigated to an empty line.
+ #
+ speech.speak(_("blank"))
+ elif wordString.isspace():
+ # Translators: "white space" is a short phrase to mean the
+ # user has navigated to a line with only whitespace on it.
+ #
+ speech.speak(_("white space"))
+ elif wordString.decode("UTF-8").isupper() and speechType == 1:
+ speech.speak(wordString,
+ self.voices[settings.UPPERCASE_VOICE])
+ elif speechType == 2:
+ self.spellCurrentItem(wordString)
+ elif speechType == 3:
+ self.phoneticSpellCurrentItem(wordString)
+ elif speechType == 1:
+ wordString = self.utilities.adjustForRepeats(wordString)
+ speech.speak(wordString)
+
+ self.updateBrailleReview(targetCursorCell)
+
+ return True
+
+ def reviewCurrentAccessible(self, inputEvent):
+ context = self.getFlatReviewContext()
+ [zoneString, x, y, width, height] = \
+ context.getCurrent(flat_review.Context.ZONE)
+ self.drawOutline(x, y, width, height)
+
+ # Don't announce anything from speech if the user used
+ # the Braille display as an input device.
+ #
+ if not isinstance(inputEvent, input_event.BrailleEvent):
+ utterances = self.speechGenerator.generateSpeech(
+ context.getCurrentAccessible())
+ utterances.extend(self.tutorialGenerator.getTutorial(
+ context.getCurrentAccessible(), False))
+ speech.speak(utterances)
+ return True
+
+ def reviewPreviousItem(self, inputEvent):
+ """Moves the flat review context to the previous item. Places
+ the flat review cursor at the beginning of the item."""
+
+ context = self.getFlatReviewContext()
+
+ moved = context.goPrevious(flat_review.Context.WORD,
+ flat_review.Context.WRAP_LINE)
+
+ if moved:
+ self._reviewCurrentItem(inputEvent)
+ self.targetCursorCell = self.getBrailleCursorCell()
+
+ return True
+
+ def reviewNextItem(self, inputEvent):
+ """Moves the flat review context to the next item. Places
+ the flat review cursor at the beginning of the item."""
+
+ context = self.getFlatReviewContext()
+
+ moved = context.goNext(flat_review.Context.WORD,
+ flat_review.Context.WRAP_LINE)
+
+ if moved:
+ self._reviewCurrentItem(inputEvent)
+ self.targetCursorCell = self.getBrailleCursorCell()
+
+ return True
+
+ def reviewCurrentCharacter(self, inputEvent):
+ """Brailles and speaks the current flat review character."""
+
+ self._reviewCurrentCharacter(inputEvent, 1)
+ self.lastReviewCurrentEvent = inputEvent
+
+ return True
+
+ def reviewSpellCurrentCharacter(self, inputEvent):
+ """Brailles and 'spells' (phonetically) the current flat review
+ character.
+ """
+
+ self._reviewCurrentCharacter(inputEvent, 2)
+ self.lastReviewCurrentEvent = inputEvent
+
+ return True
+
+ def reviewUnicodeCurrentCharacter(self, inputEvent):
+ """Brailles and speaks unicode information about the current flat
+ review character.
+ """
+
+ self._reviewCurrentCharacter(inputEvent, 3)
+ self.lastReviewCurrentEvent = inputEvent
+
+ return True
+
+ def _reviewCurrentCharacter(self, inputEvent, speechType=1):
+ """Presents the current flat review character via braille and speech.
+
+ Arguments:
+ - inputEvent - the current input event.
+ - speechType - the desired presentation:
+ speak (1),
+ phonetic (2)
+ unicode value information (3)
+ """
+
+ context = self.getFlatReviewContext()
+
+ [charString, x, y, width, height] = \
+ context.getCurrent(flat_review.Context.CHAR)
+ self.drawOutline(x, y, width, height)
+
+ # Don't announce anything from speech if the user used
+ # the Braille display as an input device.
+ #
+ if not isinstance(inputEvent, input_event.BrailleEvent):
+ if (not charString) or (not len(charString)):
+ # Translators: "blank" is a short word to mean the
+ # user has navigated to an empty line.
+ #
+ speech.speak(_("blank"))
+ else:
+ [lineString, x, y, width, height] = \
+ context.getCurrent(flat_review.Context.LINE)
+ if lineString == "\n" and speechType != 3:
+ # Translators: "blank" is a short word to mean the
+ # user has navigated to an empty line.
+ #
+ speech.speak(_("blank"))
+ elif speechType == 3:
+ self.speakUnicodeCharacter(charString)
+ elif speechType == 2:
+ self.phoneticSpellCurrentItem(charString)
+ elif charString.decode("UTF-8").isupper():
+ speech.speakCharacter(charString,
+ self.voices[settings.UPPERCASE_VOICE])
+ else:
+ speech.speakCharacter(charString)
+
+ self.updateBrailleReview()
+
+ return True
+
+ def reviewPreviousCharacter(self, inputEvent):
+ """Moves the flat review context to the previous character. Places
+ the flat review cursor at character."""
+
+ context = self.getFlatReviewContext()
+
+ moved = context.goPrevious(flat_review.Context.CHAR,
+ flat_review.Context.WRAP_LINE)
+
+ if moved:
+ self._reviewCurrentCharacter(inputEvent)
+ self.targetCursorCell = self.getBrailleCursorCell()
+
+ return True
+
+ def reviewEndOfLine(self, inputEvent):
+ """Moves the flat review context to the end of the line. Places
+ the flat review cursor at the end of the line."""
+
+ context = self.getFlatReviewContext()
+ context.goEnd(flat_review.Context.LINE)
+
+ self.reviewCurrentCharacter(inputEvent)
+ self.targetCursorCell = self.getBrailleCursorCell()
+
+ return True
+
+ def reviewNextCharacter(self, inputEvent):
+ """Moves the flat review context to the next character. Places
+ the flat review cursor at character."""
+
+ context = self.getFlatReviewContext()
+
+ moved = context.goNext(flat_review.Context.CHAR,
+ flat_review.Context.WRAP_LINE)
+
+ if moved:
+ self._reviewCurrentCharacter(inputEvent)
+ self.targetCursorCell = self.getBrailleCursorCell()
+
+ return True
+
+ def reviewAbove(self, inputEvent):
+ """Moves the flat review context to the character most directly
+ above the current flat review cursor. Places the flat review
+ cursor at character."""
+
+ context = self.getFlatReviewContext()
+
+ moved = context.goAbove(flat_review.Context.CHAR,
+ flat_review.Context.WRAP_LINE)
+
+ if moved:
+ self._reviewCurrentItem(inputEvent, self.targetCursorCell)
+
+ return True
+
+ def reviewBelow(self, inputEvent):
+ """Moves the flat review context to the character most directly
+ below the current flat review cursor. Places the flat review
+ cursor at character."""
+
+ context = self.getFlatReviewContext()
+
+ moved = context.goBelow(flat_review.Context.CHAR,
+ flat_review.Context.WRAP_LINE)
+
+ if moved:
+ self._reviewCurrentItem(inputEvent, self.targetCursorCell)
+
+ return True
+
+ def reviewCurrentLine(self, inputEvent):
+ """Brailles and speaks the current flat review line."""
+
+ self._reviewCurrentLine(inputEvent, 1)
+ self.lastReviewCurrentEvent = inputEvent
+
+ return True
+
+ def reviewSpellCurrentLine(self, inputEvent):
+ """Brailles and spells the current flat review line."""
+
+ self._reviewCurrentLine(inputEvent, 2)
+ self.lastReviewCurrentEvent = inputEvent
+
+ return True
+
+ def reviewPhoneticCurrentLine(self, inputEvent):
+ """Brailles and phonetically spells the current flat review line."""
+
+ self._reviewCurrentLine(inputEvent, 3)
+ self.lastReviewCurrentEvent = inputEvent
+
+ return True
+
+ def _reviewCurrentLine(self, inputEvent, speechType=1):
+ """Presents the current flat review line via braille and speech.
+
+ Arguments:
+ - inputEvent - the current input event.
+ - speechType - the desired presentation: speak (1), spell (2), or
+ phonetic (3)
+ """
+
+ context = self.getFlatReviewContext()
+
+ [lineString, x, y, width, height] = \
+ context.getCurrent(flat_review.Context.LINE)
+ self.drawOutline(x, y, width, height)
+
+ # Don't announce anything from speech if the user used
+ # the Braille display as an input device.
+ #
+ if not isinstance(inputEvent, input_event.BrailleEvent):
+ if (not lineString) \
+ or (not len(lineString)) \
+ or (lineString == "\n"):
+ # Translators: "blank" is a short word to mean the
+ # user has navigated to an empty line.
+ #
+ speech.speak(_("blank"))
+ elif lineString.isspace():
+ # Translators: "white space" is a short phrase to mean the
+ # user has navigated to a line with only whitespace on it.
+ #
+ speech.speak(_("white space"))
+ elif lineString.decode("UTF-8").isupper() \
+ and (speechType < 2 or speechType > 3):
+ speech.speak(lineString, self.voices[settings.UPPERCASE_VOICE])
+ elif speechType == 2:
+ self.spellCurrentItem(lineString)
+ elif speechType == 3:
+ self.phoneticSpellCurrentItem(lineString)
+ else:
+ lineString = self.utilities.adjustForRepeats(lineString)
+ speech.speak(lineString)
+
+ self.updateBrailleReview()
+
+ return True
+
+ def reviewPreviousLine(self, inputEvent):
+ """Moves the flat review context to the beginning of the
+ previous line."""
+
+ context = self.getFlatReviewContext()
+
+ moved = context.goPrevious(flat_review.Context.LINE,
+ flat_review.Context.WRAP_LINE)
+
+ if moved:
+ self._reviewCurrentLine(inputEvent)
+ self.targetCursorCell = self.getBrailleCursorCell()
+
+ return True
+
+ def reviewHome(self, inputEvent):
+ """Moves the flat review context to the top left of the current
+ window."""
+
+ context = self.getFlatReviewContext()
+
+ context.goBegin()
+
+ self._reviewCurrentLine(inputEvent)
+ self.targetCursorCell = self.getBrailleCursorCell()
+
+ return True
+
+ def reviewNextLine(self, inputEvent):
+ """Moves the flat review context to the beginning of the
+ next line. Places the flat review cursor at the beginning
+ of the line."""
+
+ context = self.getFlatReviewContext()
+
+ moved = context.goNext(flat_review.Context.LINE,
+ flat_review.Context.WRAP_LINE)
+
+ if moved:
+ self._reviewCurrentLine(inputEvent)
+ self.targetCursorCell = self.getBrailleCursorCell()
+
+ return True
+
+ def reviewBottomLeft(self, inputEvent):
+ """Moves the flat review context to the beginning of the
+ last line in the window. Places the flat review cursor at
+ the beginning of the line."""
+
+ context = self.getFlatReviewContext()
+
+ context.goEnd(flat_review.Context.WINDOW)
+ context.goBegin(flat_review.Context.LINE)
+ self._reviewCurrentLine(inputEvent)
+ self.targetCursorCell = self.getBrailleCursorCell()
+
+ return True
+
+ def reviewEnd(self, inputEvent):
+ """Moves the flat review context to the end of the
+ last line in the window. Places the flat review cursor
+ at the end of the line."""
+
+ context = self.getFlatReviewContext()
+ context.goEnd()
+
+ self._reviewCurrentLine(inputEvent)
+ self.targetCursorCell = self.getBrailleCursorCell()
+
+ return True
+
+ def reviewCurrentItem(self, inputEvent, targetCursorCell=0):
+ """Brailles and speaks the current item to the user."""
+
+ self._reviewCurrentItem(inputEvent, targetCursorCell, 1)
+ self.lastReviewCurrentEvent = inputEvent
+
+ return True
+
+ def reviewSpellCurrentItem(self, inputEvent, targetCursorCell=0):
+ """Brailles and spells the current item to the user."""
+
+ self._reviewCurrentItem(inputEvent, targetCursorCell, 2)
+ self.lastReviewCurrentEvent = inputEvent
+
+ return True
+
+ def reviewPhoneticCurrentItem(self, inputEvent, targetCursorCell=0):
+ """Brailles and phonetically spells the current item to the user."""
+
+ self._reviewCurrentItem(inputEvent, targetCursorCell, 3)
+ self.lastReviewCurrentEvent = inputEvent
+
+ return True
+
+ def showZones(self, inputEvent):
+ """Debug routine to paint rectangles around the discrete
+ interesting (e.g., text) zones in the active window for
+ this application.
+ """
+
+ flatReviewContext = self.getFlatReviewContext()
+ lines = flatReviewContext.lines
+ for line in lines:
+ lineString = ""
+ for zone in line.zones:
+ lineString += " '%s' [%s]" % \
+ (zone.string, zone.accessible.getRoleName())
+ debug.println(debug.LEVEL_OFF, lineString)
+ self.flatReviewContext = None
+
+ def sayAll(self, inputEvent):
+ clickCount = self.getClickCount()
+ doubleClick = clickCount == 2
+ self.lastSayAllEvent = inputEvent
+
+ if doubleClick:
+ # Try to "say all" for the current dialog/window by flat
+ # reviewing everything. See bug #354462 for more details.
+ #
+ context = self.getFlatReviewContext()
+
+ utterances = []
+ context.goBegin()
+ while True:
+ [wordString, x, y, width, height] = \
+ context.getCurrent(flat_review.Context.ZONE)
+
+ utterances.append(wordString)
+
+ moved = context.goNext(flat_review.Context.ZONE,
+ flat_review.Context.WRAP_LINE)
+
+ if not moved:
+ break
+
+ speech.speak(utterances)
+
+ elif self.utilities.isTextArea(orca_state.locusOfFocus):
+ try:
+ orca_state.locusOfFocus.queryText()
+ except NotImplementedError:
+ utterances = self.speechGenerator.generateSpeech(
+ orca_state.locusOfFocus)
+ utterances.extend(self.tutorialGenerator.getTutorial(
+ orca_state.locusOfFocus, False))
+ speech.speak(utterances)
+ except AttributeError:
+ pass
+ else:
+ speech.sayAll(self.textLines(orca_state.locusOfFocus),
+ self.__sayAllProgressCallback)
+
+ return True
+
+ def toggleFlatReviewMode(self, inputEvent=None):
+ """Toggles between flat review mode and focus tracking mode."""
+
+ if self.flatReviewContext:
+ if inputEvent:
+ # Translators: the 'flat review' feature of Orca
+ # allows the blind user to explore the text in a
+ # window in a 2D fashion. That is, Orca treats all
+ # the text from all objects in a window (e.g.,
+ # buttons, labels, etc.) as a sequence of words in a
+ # sequence of lines. The flat review feature allows
+ # the user to explore this text by the {previous,next}
+ # {line,word,character}. This message lets the user know
+ # they have left the flat review feature.
+ #
+ if settings.speechVerbosityLevel \
+ != settings.VERBOSITY_LEVEL_BRIEF:
+ speech.speak(_("Leaving flat review."))
+ self.drawOutline(-1, 0, 0, 0)
+ self.flatReviewContext = None
+ self.updateBraille(orca_state.locusOfFocus)
+ else:
+ if inputEvent:
+ # Translators: the 'flat review' feature of Orca
+ # allows the blind user to explore the text in a
+ # window in a 2D fashion. That is, Orca treats all
+ # the text from all objects in a window (e.g.,
+ # buttons, labels, etc.) as a sequence of words in a
+ # sequence of lines. The flat review feature allows
+ # the user to explore this text by the {previous,next}
+ # {line,word,character}. This message lets the user know
+ # they have entered the flat review feature.
+ #
+ if settings.speechVerbosityLevel \
+ != settings.VERBOSITY_LEVEL_BRIEF:
+ speech.speak(_("Entering flat review."))
+ context = self.getFlatReviewContext()
+ [wordString, x, y, width, height] = \
+ context.getCurrent(flat_review.Context.WORD)
+ self.drawOutline(x, y, width, height)
+ self._reviewCurrentItem(inputEvent, self.targetCursorCell)
+
+ return True
+
+ def toggleSpeakingIndentationJustification(self, inputEvent=None):
+ """Toggles the speaking of indentation and justification."""
+
+ settings.enableSpeechIndentation = not settings.enableSpeechIndentation
+ if settings.enableSpeechIndentation :
+ # Translators: A message indicating that
+ # indentation and justification will be spoken.
+ #
+ line = _("Speaking of indentation and justification enabled.")
+ else:
+ # Translators: A message indicating that
+ # indentation and justification will not be spoken.
+ #
+ line = _("Speaking of indentation and justification disabled.")
+
+ speech.speak(line)
+
+ return True
+
+ def toggleTableCellReadMode(self, inputEvent=None):
+ """Toggles an indicator for whether we should just read the current
+ table cell or read the whole row."""
+
+ settings.readTableCellRow = not settings.readTableCellRow
+ if settings.readTableCellRow:
+ # Translators: when users are navigating a table, they
+ # sometimes want the entire row of a table read, or
+ # they just want the current cell to be presented to them.
+ #
+ line = _("Speak row")
+ else:
+ # Translators: when users are navigating a table, they
+ # sometimes want the entire row of a table read, or
+ # they just want the current cell to be presented to them.
+ #
+ line = _("Speak cell")
+
+ speech.speak(line)
+
+ return True
+
+ def doWhereAmI(self, inputEvent, basicOnly):
+ """Peforms the whereAmI operation.
+
+ Arguments:
+ - inputEvent: The original inputEvent
+ """
+
+ obj = orca_state.locusOfFocus
+ self.updateBraille(obj)
+
+ return self.whereAmI.whereAmI(obj, basicOnly)
+
+ def whereAmIBasic(self, inputEvent):
+ """Speaks basic information about the current object of interest.
+ """
+
+ self.doWhereAmI(inputEvent, True)
+
+ def whereAmIDetailed(self, inputEvent):
+ """Speaks detailed/custom information about the current object of
+ interest.
+ """
+
+ self.doWhereAmI(inputEvent, False)
+
+ def printMemoryUsageHandler(self, inputEvent):
+ """Prints memory usage information."""
+ print 'TODO: print something useful for memory debugging'
+
+ def printAppsHandler(self, inputEvent=None):
+ """Prints a list of all applications to stdout."""
+
+ self.utilities.printApps()
+ return True
+
+ def printAncestryHandler(self, inputEvent=None):
+ """Prints the ancestry for the current locusOfFocus"""
+
+ self.utilities.printAncestry(orca_state.locusOfFocus)
+ return True
+
+ def printHierarchyHandler(self, inputEvent=None):
+ """Prints the application for the current locusOfFocus"""
+
+ if orca_state.locusOfFocus:
+ self.utilities.printHierarchy(
+ orca_state.locusOfFocus.getApplication(),
+ orca_state.locusOfFocus)
+
+ return True
+
+ ########################################################################
+ # #
# AT-SPI OBJECT EVENT HANDLERS #
# #
########################################################################
+ def noOp(self, event):
+ """Just here to capture events.
+
+ Arguments:
+ - event: the Event
+ """
+ pass
+
+ def onActiveDescendantChanged(self, event):
+ """Called when an object who manages its own descendants detects a
+ change in one of its children.
+
+ Arguments:
+ - event: the Event
+ """
+
+ if not event.source.getState().contains(pyatspi.STATE_FOCUSED):
+ return
+
+ # There can be cases when the object that fires an
+ # active-descendant-changed event has no children. In this case,
+ # use the object that fired the event, otherwise, use the child.
+ #
+ child = event.any_data
+ if child:
+ if self.stopSpeechOnActiveDescendantChanged(event):
+ speech.stop()
+ orca.setLocusOfFocus(event, child)
+ else:
+ orca.setLocusOfFocus(event, event.source)
+
+ # We'll tuck away the activeDescendant information for future
+ # reference since the AT-SPI gives us little help in finding
+ # this.
+ #
+ if orca_state.locusOfFocus \
+ and (orca_state.locusOfFocus != event.source):
+ self.pointOfReference['activeDescendantInfo'] = \
+ [orca_state.locusOfFocus.parent,
+ orca_state.locusOfFocus.getIndexInParent()]
+
+ def onCaretMoved(self, event):
+ """Called whenever the caret moves.
+
+ Arguments:
+ - event: the Event
+ """
+
+ # Ignore caret movements from non-focused objects, unless the
+ # currently focused object is the parent of the object which
+ # has the caret.
+ #
+ if (event.source != orca_state.locusOfFocus) \
+ and (event.source.parent != orca_state.locusOfFocus):
+ return
+
+ # We always automatically go back to focus tracking mode when
+ # the caret moves in the focused object.
+ #
+ if self.flatReviewContext:
+ self.toggleFlatReviewMode()
+
+ self._presentTextAtNewCaretPosition(event)
+
def onFocus(self, event):
"""Called whenever an object gets focus.
@@ -3334,6 +3628,58 @@ class Script(script.Script):
orca.setLocusOfFocus(event, newFocus)
+ def onLinkSelected(self, event):
+ """Called when a hyperlink is selected in a text area.
+
+ Arguments:
+ - event: the Event
+ """
+
+ # [[[TODO: WDW - HACK one might think we could expect an
+ # application to keep its name, but it appears as though
+ # yelp has an identity problem and likes to start calling
+ # itself "yelp," but then changes its name to "Mozilla"
+ # on Fedora Core 4 after the user selects a link. So, we'll
+ # just assume that link-selected events always come from the
+ # application with focus.]]]
+ #
+ #if orca_state.locusOfFocus \
+ # and (orca_state.locusOfFocus.app == event.source.app):
+ # orca.setLocusOfFocus(event, event.source)
+ orca.setLocusOfFocus(event, event.source)
+
+ def onMouseButton(self, event):
+ """Called whenever the user presses or releases a mouse button.
+
+ Arguments:
+ - event: the Event
+ """
+
+ # If we've received a mouse button released event, then check if
+ # there are and text selections for the locus of focus and speak
+ # them.
+ #
+ state = event.type[-1]
+ if state == "r":
+ obj = orca_state.locusOfFocus
+ try:
+ text = obj.queryText()
+ except:
+ pass
+ else:
+ [textContents, startOffset, endOffset] = \
+ self.utilities.allSelectedText(obj)
+ if textContents:
+ utterances = []
+ utterances.append(textContents)
+
+ # Translators: when the user selects (highlights) text in
+ # a document, Orca lets them know this.
+ #
+ utterances.append(C_("text", "selected"))
+ speech.speak(utterances)
+ self.updateBraille(orca_state.locusOfFocus)
+
def onNameChanged(self, event):
"""Called whenever a property on an object changes.
@@ -3363,273 +3709,213 @@ class Script(script.Script):
self.pointOfReference['oldName'] = event.source.name
orca.visualAppearanceChanged(event, event.source)
- 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
- with a RELATION_FLOWS_FROM relationship. If they pressed Shift-Up,
- then look for a RELATION_FLOWS_TO relationship.
+ def onSelectionChanged(self, event):
+ """Called when an object's selection changes.
Arguments:
- - the current text object
- - the flows relationship (RELATION_FLOWS_FROM or RELATION_FLOWS_TO).
-
- Returns an indication of whether anything was spoken.
+ - event: the Event
"""
- lastPos = self.pointOfReference.get("lastCursorPosition")
-
- # Reasons to NOT speak contiguous selections:
- #
- # 1. The new cursor position is in the same object as the old
- # cursor position. (The change in selection is all within
- # the current object.)
- # 2. If we are selecting up line by line from the beginning of
- # the line and have just crossed into a new object, the change
- # in selection is the previous line (which has just become
- # selected). Nothing has changed on the line we came from.
- #
- if self.isSameObject(lastPos[0], obj) \
- or relationship == pyatspi.RELATION_FLOWS_TO and lastPos[1] == 0:
- return False
-
- selSpoken = False
- current = obj
- for relation in current.getRelationSet():
- if relation.getRelationType() == relationship:
- obj = relation.getTarget(0)
- objText = obj.queryText()
-
- # 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
- # the line.
- #
- if relationship == pyatspi.RELATION_FLOWS_FROM:
- start, end = lastPos[1], objText.characterCount
-
- # When selecting up across paragraph boundaries, what
- # we've (un)selected on (what is now) the next line is
- # from the beginning of the line to wherever the cursor
- # used to be.
- #
- else:
- start, end = 0, lastPos[1]
-
- if objText.getNSelections() > 0:
- [textContents, startOffset, endOffset] = \
- self.getSelectedText(obj)
-
- # Now that we have the full selection, adjust based
- # on the relation type. (see above comment)
- #
- startOffset = start or startOffset
- endOffset = end or endOffset
- self.sayPhrase(obj, startOffset, endOffset)
- selSpoken = True
- else:
- # We don't have selections in this object. But we're
- # here, which means that something is selected in a
- # neighboring object and the text in this object must
- # have just become unselected and needs to be spoken.
- #
- self.sayPhrase(obj, start, end)
- selSpoken = True
-
- return selSpoken
+ if not event or not event.source:
+ return
- def _presentTextAtNewCaretPosition(self, event, otherObj=None):
- """Updates braille, magnification, and outputs speech for the
- event.source or the otherObj."""
+ # Save the event source, if it is a menu or combo box. It will be
+ # useful for optimizing componentAtDesktopCoords in the case that
+ # the pointer is hovering over a menu item. The alternative is to
+ # traverse the application's tree looking for potential moused-over
+ # menu items.
+ if event.source.getRole() in (pyatspi.ROLE_COMBO_BOX,
+ pyatspi.ROLE_MENU):
+ self.lastSelectedMenu = event.source
- obj = otherObj or event.source
- text = obj.queryText()
+ # Avoid doing this with objects that manage their descendants
+ # because they'll issue a descendant changed event.
+ #
+ if event.source.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS):
+ return
- if obj:
- mag.magnifyAccessible(event, obj)
+ if event.source.getRole() == pyatspi.ROLE_COMBO_BOX:
+ orca.visualAppearanceChanged(event, event.source)
- # Update the Braille display - if we can just reposition
- # the cursor, then go for it.
+ # We treat selected children as the locus of focus. When the
+ # selection changed we want to update the locus of focus. If
+ # there is no selection, we default the locus of focus to the
+ # containing object.
#
- brailleNeedsRepainting = True
- line = braille.getShowingLine()
- for region in line.regions:
- if isinstance(region, braille.Text) \
- and (region.accessible == obj):
- if region.repositionCursor():
- self.refreshBraille(True)
- brailleNeedsRepainting = False
- break
+ elif (event.source != orca_state.locusOfFocus) and \
+ event.source.getState().contains(pyatspi.STATE_FOCUSED):
+ newFocus = event.source
+ if event.source.childCount:
+ selection = event.source.querySelection()
+ if selection.nSelectedChildren > 0:
+ newFocus = selection.getSelectedChild(0)
- if brailleNeedsRepainting:
- self.updateBraille(obj)
+ orca.setLocusOfFocus(event, newFocus)
- if not orca_state.lastInputEvent:
- return
+ def onStateChanged(self, event):
+ """Called whenever an object's state changes.
- if isinstance(orca_state.lastInputEvent, input_event.MouseButtonEvent):
- if not orca_state.lastInputEvent.pressed:
- self.sayLine(obj)
- return
+ Arguments:
+ - event: the Event
+ """
- # Guess why the caret moved and say something appropriate.
- # [[[TODO: WDW - this motion assumes traditional GUI
- # navigation gestures. In an editor such as vi, line up and
- # down is done via other actions such as "i" or "j". We may
- # need to think about this a little harder.]]]
+ # Do we care?
#
- if not isinstance(orca_state.lastInputEvent,
- input_event.KeyboardEvent):
- return
-
- keyString = orca_state.lastNonModifierKeyEvent.event_string
- mods = orca_state.lastInputEvent.modifiers
- isControlKey = mods & settings.CTRL_MODIFIER_MASK
- isShiftKey = mods & settings.SHIFT_MODIFIER_MASK
- lastPos = self.pointOfReference.get("lastCursorPosition")
- hasLastPos = (lastPos != None)
+ if event.type.startswith("object:state-changed:active"):
+ if self.findCommandRun:
+ self.findCommandRun = False
+ self.find()
+ return
- if (keyString == "Up") or (keyString == "Down"):
- # If the user has typed Shift-Up or Shift-Down, then we want
- # to speak the text that has just been selected or unselected,
- # otherwise we speak the new line where the text cursor is
- # currently positioned.
+ if event.type.startswith("object:state-changed:selected") \
+ and orca_state.locusOfFocus:
+ # If this selection state change is for the object which
+ # currently has the locus of focus, and the last keyboard
+ # event was Space, or we are a focused table cell and we
+ # arrowed Down or Up and are now selected, then let the
+ # user know the selection state.
+ # See bugs #486908 and #519564 for more details.
#
- if hasLastPos and isShiftKey and not isControlKey:
- if keyString == "Up":
- # If we have just crossed a paragraph boundary with
- # 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
- else:
- [startOffset, endOffset] \
- = self.getOffsetsForPhrase(obj)
- self.sayPhrase(obj, startOffset, endOffset)
- selSpoken = self._speakContiguousSelection(obj,
- pyatspi.RELATION_FLOWS_TO)
+ if isinstance(orca_state.lastInputEvent,
+ input_event.KeyboardEvent):
+ if orca_state.lastNonModifierKeyEvent:
+ keyString = orca_state.lastNonModifierKeyEvent.event_string
else:
- selSpoken = self._speakContiguousSelection(obj,
- pyatspi.RELATION_FLOWS_FROM)
-
- # If we have just crossed a paragraph boundary with
- # Shift+Down, what we've selected in this object starts
- # with the beginning of the paragraph and goes to the
- # current offset.
- #
- if not self.isSameObject(lastPos[0], obj):
- [startOffset, endOffset] = 0, text.caretOffset
- else:
- [startOffset, endOffset] \
- = self.getOffsetsForPhrase(obj)
-
- if startOffset != endOffset:
- self.sayPhrase(obj, startOffset, endOffset)
-
- else:
- [startOffset, endOffset] = self.getOffsetsForLine(obj)
- self.sayLine(obj)
+ keyString = None
+ mods = orca_state.lastInputEvent.modifiers
+ isControlKey = mods & settings.CTRL_MODIFIER_MASK
+ state = orca_state.locusOfFocus.getState()
+ announceState = False
- elif (keyString == "Left") or (keyString == "Right"):
- # If the user has typed Control-Shift-Up or Control-Shift-Dowm,
- # then we want to speak the text that has just been selected
- # or unselected, otherwise if the user has typed Control-Left
- # or Control-Right, we speak the current word otherwise we speak
- # the character at the text cursor position.
- #
- inNewObj = hasLastPos and not self.isSameObject(lastPos[0], obj)
+ if state.contains(pyatspi.STATE_FOCUSED) and \
+ self.utilities.isSameObject(
+ event.source, orca_state.locusOfFocus):
- if hasLastPos and not inNewObj and isShiftKey and isControlKey:
- [startOffset, endOffset] = self.getOffsetsForPhrase(obj)
- self.sayPhrase(obj, startOffset, endOffset)
- elif isControlKey and not isShiftKey:
- [startOffset, endOffset] = self.getOffsetsForWord(obj)
- if startOffset == endOffset:
- self.sayCharacter(obj)
- else:
- self.sayWord(obj)
- else:
- [startOffset, endOffset] = self.getOffsetsForChar(obj)
- self.sayCharacter(obj)
+ if keyString == "space":
+ if isControlKey:
+ announceState = True
+ else:
+ # Weed out a bogus situation. If we are already
+ # selected and the user presses "space" again,
+ # we don't want to speak the intermediate
+ # "unselected" state.
+ #
+ eventState = event.source.getState()
+ selected = eventState.contains(\
+ pyatspi.STATE_SELECTED)
+ announceState = (selected and event.detail1)
- elif keyString == "Page_Up":
- # If the user has typed Control-Shift-Page_Up, then we want
- # to speak the text that has just been selected or unselected,
- # otherwise if the user has typed Control-Page_Up, then we
- # speak the character to the right of the current text cursor
- # position otherwise we speak the current line.
- #
- if hasLastPos and isShiftKey and isControlKey:
- [startOffset, endOffset] = self.getOffsetsForPhrase(obj)
- self.sayPhrase(obj, startOffset, endOffset)
- elif isControlKey:
- [startOffset, endOffset] = self.getOffsetsForChar(obj)
- self.sayCharacter(obj)
- else:
- [startOffset, endOffset] = self.getOffsetsForLine(obj)
- self.sayLine(obj)
+ if (keyString == "Down" or keyString == "Up") \
+ and event.source.getRole() == pyatspi.ROLE_TABLE_CELL \
+ and state.contains(pyatspi.STATE_SELECTED):
+ announceState = True
- elif keyString == "Page_Down":
- # If the user has typed Control-Shift-Page_Down, then we want
- # to speak the text that has just been selected or unselected,
- # otherwise if the user has just typed Page_Down, then we speak
- # the current line.
- #
- if hasLastPos and isShiftKey and isControlKey:
- [startOffset, endOffset] = self.getOffsetsForPhrase(obj)
- self.sayPhrase(obj, startOffset, endOffset)
- else:
- [startOffset, endOffset] = self.getOffsetsForLine(obj)
- self.sayLine(obj)
+ if announceState:
+ if event.detail1:
+ # Translators: this object is now selected.
+ # Let the user know this.
+ #
+ #
+ speech.speak(C_("text", "selected"), None, False)
+ else:
+ # Translators: this object is now unselected.
+ # Let the user know this.
+ #
+ #
+ speech.speak(C_("text", "unselected"), None, False)
+ return
- elif (keyString == "Home") or (keyString == "End"):
- # If the user has typed Shift-Home or Shift-End, then we want
- # to speak the text that has just been selected or unselected,
- # otherwise if the user has typed Control-Home or Control-End,
- # then we speak the current line otherwise we speak the character
- # to the right of the current text cursor position.
- #
- if hasLastPos and isShiftKey and not isControlKey:
- [startOffset, endOffset] = self.getOffsetsForPhrase(obj)
- self.sayPhrase(obj, startOffset, endOffset)
- elif isControlKey:
- [startOffset, endOffset] = self.getOffsetsForLine(obj)
- self.sayLine(obj)
- else:
- [startOffset, endOffset] = self.getOffsetsForChar(obj)
- self.sayCharacter(obj)
+ if event.type.startswith("object:state-changed:focused"):
+ iconified = False
+ try:
+ window = self.utilities.topLevelObject(event.source)
+ iconified = window.getState().contains(pyatspi.STATE_ICONIFIED)
+ except:
+ debug.println(debug.LEVEL_FINEST,
+ "onStateChanged: could not get frame of focused item")
+ if not iconified:
+ if event.detail1:
+ self.onFocus(event)
+ # We don't set locus of focus of None here because it
+ # wreaks havoc on the code that determines the context
+ # when you tab from widget to widget. For example,
+ # tabbing between panels in the gtk-demo buttons demo.
+ #
+ #else:
+ # orca.setLocusOfFocus(event, None)
+ return
- else:
- startOffset = text.caretOffset
- endOffset = text.caretOffset
+ # Handle tooltip popups.
+ #
+ if event.source.getRole() == pyatspi.ROLE_TOOL_TIP:
+ obj = event.source
+ if event.type.startswith("object:state-changed:showing"):
+ if event.detail1 == 1:
+ self.presentToolTip(obj)
+ elif orca_state.locusOfFocus \
+ and isinstance(orca_state.lastInputEvent,
+ input_event.KeyboardEvent) \
+ and (orca_state.lastNonModifierKeyEvent.event_string \
+ == "F1"):
+ self.updateBraille(orca_state.locusOfFocus)
+ utterances = self.speechGenerator.generateSpeech(
+ orca_state.locusOfFocus)
+ utterances.extend(self.tutorialGenerator.getTutorial(
+ orca_state.locusOfFocus, False))
+ speech.speak(utterances)
+ return
- self._saveLastCursorPosition(obj, text.caretOffset)
- self._saveSpokenTextRange(startOffset, endOffset)
+ if event.source.getRole() in state_change_notifiers:
+ notifiers = state_change_notifiers[event.source.getRole()]
+ found = False
+ for state in notifiers:
+ if state and event.type.endswith(state):
+ found = True
+ break
+ if found:
+ orca.visualAppearanceChanged(event, event.source)
- def onCaretMoved(self, event):
- """Called whenever the caret moves.
+ def onTextAttributesChanged(self, event):
+ """Called when an object's text attributes change. Right now this
+ method is only to handle the presentation of spelling errors on
+ the fly. Also note that right now, the Gecko toolkit is the only
+ one to present this information to us.
Arguments:
- event: the Event
"""
- # Ignore caret movements from non-focused objects, unless the
- # currently focused object is the parent of the object which
- # has the caret.
- #
- if (event.source != orca_state.locusOfFocus) \
- and (event.source.parent != orca_state.locusOfFocus):
- return
+ if settings.speechVerbosityLevel == settings.VERBOSITY_LEVEL_VERBOSE \
+ and self.utilities.isSameObject(
+ event.source, orca_state.locusOfFocus):
+ try:
+ text = event.source.queryText()
+ except:
+ return
- # We always automatically go back to focus tracking mode when
- # the caret moves in the focused object.
- #
- if self.flatReviewContext:
- self.toggleFlatReviewMode()
+ # If the misspelled word indicator has just appeared, it's
+ # because the user typed a word boundary or navigated out
+ # of the word. We don't want to have to store a full set of
+ # each object's text attributes to compare, therefore, we'll
+ # check the previous word (most likely case) and the next
+ # word with respect to the current position.
+ #
+ prevWordAndOffsets = \
+ text.getTextAtOffset(text.caretOffset - 1,
+ pyatspi.TEXT_BOUNDARY_WORD_START)
+ nextWordAndOffsets = \
+ text.getTextAtOffset(text.caretOffset + 1,
+ pyatspi.TEXT_BOUNDARY_WORD_START)
- self._presentTextAtNewCaretPosition(event)
+ if self.utilities.isWordMisspelled(
+ event.source, prevWordAndOffsets[1] ) \
+ or self.utilities.isWordMisspelled(
+ event.source, nextWordAndOffsets[1]):
+ # Translators: this is to inform the user of the presence
+ # of the red squiggly line which indicates that a given
+ # word is not spelled correctly.
+ #
+ speech.speak(_("misspelled"))
def onTextDeleted(self, event):
"""Called whenever text is deleted from an object.
@@ -3700,7 +3986,7 @@ class Script(script.Script):
else:
return
- if self.getLinkIndex(event.source, text.caretOffset) >= 0:
+ if self.utilities.linkIndex(event.source, text.caretOffset) >= 0:
voice = self.voices[settings.HYPERLINK_VOICE]
elif character.decode("UTF-8").isupper():
voice = self.voices[settings.UPPERCASE_VOICE]
@@ -3716,30 +4002,6 @@ class Script(script.Script):
else:
speech.speak(character, voice, False)
- def willEchoCharacter(self, event):
- """Given a keyboard event containing an alphanumeric key,
- determine if the script is likely to echo it as a character.
- """
-
- # The check here in English is something like this: "If this
- # character echo is enabled, then character echo is likely to
- # happen if the locus of focus is a focusable editable text
- # area or terminal and neither of the Ctrl, Alt, or Orca
- # modifiers are pressed. If that's the case, then character
- # echo will kick in for us."
- #
- return settings.enableEchoByCharacter \
- and orca_state.locusOfFocus \
- and (self.isTextArea(orca_state.locusOfFocus)\
- or orca_state.locusOfFocus.getRole() \
- == pyatspi.ROLE_ENTRY) \
- and (orca_state.locusOfFocus.getRole() \
- == pyatspi.ROLE_TERMINAL \
- or (not self.isReadOnlyTextArea(orca_state.locusOfFocus) \
- and (orca_state.locusOfFocus.getState().contains( \
- pyatspi.STATE_FOCUSABLE)))) \
- and not (event.modifiers & settings.ORCA_CTRL_MODIFIER_MASK)
-
def onTextInserted(self, event):
"""Called whenever text is inserted into an object.
@@ -3858,317 +4120,14 @@ class Script(script.Script):
[previousChar, startOffset, endOffset] = \
text.getTextAtOffset(previousOffset, pyatspi.TEXT_BOUNDARY_CHAR)
- if settings.enableEchoBySentence and \
- self.isSentenceDelimiter(currentChar, previousChar):
+ if settings.enableEchoBySentence \
+ and self.utilities.isSentenceDelimiter(currentChar, previousChar):
self.echoPreviousSentence(event.source)
- elif settings.enableEchoByWord and self.isWordDelimiter(currentChar):
+ elif settings.enableEchoByWord \
+ and self.utilities.isWordDelimiter(currentChar):
self.echoPreviousWord(event.source)
- def stopSpeechOnActiveDescendantChanged(self, event):
- """Whether or not speech should be stopped prior to setting the
- locusOfFocus in onActiveDescendantChanged.
-
- Arguments:
- - event: the Event
-
- Returns True if speech should be stopped; False otherwise.
- """
-
- return True
-
- def onActiveDescendantChanged(self, event):
- """Called when an object who manages its own descendants detects a
- change in one of its children.
-
- Arguments:
- - event: the Event
- """
-
- if not event.source.getState().contains(pyatspi.STATE_FOCUSED):
- return
-
- # There can be cases when the object that fires an
- # active-descendant-changed event has no children. In this case,
- # use the object that fired the event, otherwise, use the child.
- #
- child = event.any_data
- if child:
- if self.stopSpeechOnActiveDescendantChanged(event):
- speech.stop()
- orca.setLocusOfFocus(event, child)
- else:
- orca.setLocusOfFocus(event, event.source)
-
- # We'll tuck away the activeDescendant information for future
- # reference since the AT-SPI gives us little help in finding
- # this.
- #
- if orca_state.locusOfFocus \
- and (orca_state.locusOfFocus != event.source):
- self.pointOfReference['activeDescendantInfo'] = \
- [orca_state.locusOfFocus.parent,
- orca_state.locusOfFocus.getIndexInParent()]
-
- def onLinkSelected(self, event):
- """Called when a hyperlink is selected in a text area.
-
- Arguments:
- - event: the Event
- """
-
- # [[[TODO: WDW - HACK one might think we could expect an
- # application to keep its name, but it appears as though
- # yelp has an identity problem and likes to start calling
- # itself "yelp," but then changes its name to "Mozilla"
- # on Fedora Core 4 after the user selects a link. So, we'll
- # just assume that link-selected events always come from the
- # application with focus.]]]
- #
- #if orca_state.locusOfFocus \
- # and (orca_state.locusOfFocus.app == event.source.app):
- # orca.setLocusOfFocus(event, event.source)
- orca.setLocusOfFocus(event, event.source)
-
- def onStateChanged(self, event):
- """Called whenever an object's state changes.
-
- Arguments:
- - event: the Event
- """
-
- # Do we care?
- #
- if event.type.startswith("object:state-changed:active"):
- if self.findCommandRun:
- self.findCommandRun = False
- self.find()
- return
-
- if event.type.startswith("object:state-changed:selected") \
- and orca_state.locusOfFocus:
- # If this selection state change is for the object which
- # currently has the locus of focus, and the last keyboard
- # event was Space, or we are a focused table cell and we
- # arrowed Down or Up and are now selected, then let the
- # user know the selection state.
- # See bugs #486908 and #519564 for more details.
- #
- if isinstance(orca_state.lastInputEvent,
- input_event.KeyboardEvent):
- if orca_state.lastNonModifierKeyEvent:
- keyString = orca_state.lastNonModifierKeyEvent.event_string
- else:
- keyString = None
- mods = orca_state.lastInputEvent.modifiers
- isControlKey = mods & settings.CTRL_MODIFIER_MASK
- state = orca_state.locusOfFocus.getState()
- announceState = False
-
- if state.contains(pyatspi.STATE_FOCUSED) and \
- self.isSameObject(event.source, orca_state.locusOfFocus):
-
- if keyString == "space":
- if isControlKey:
- announceState = True
- else:
- # Weed out a bogus situation. If we are already
- # selected and the user presses "space" again,
- # we don't want to speak the intermediate
- # "unselected" state.
- #
- eventState = event.source.getState()
- selected = eventState.contains(\
- pyatspi.STATE_SELECTED)
- announceState = (selected and event.detail1)
-
- if (keyString == "Down" or keyString == "Up") \
- and event.source.getRole() == pyatspi.ROLE_TABLE_CELL \
- and state.contains(pyatspi.STATE_SELECTED):
- announceState = True
-
- if announceState:
- if event.detail1:
- # Translators: this object is now selected.
- # Let the user know this.
- #
- #
- speech.speak(C_("text", "selected"), None, False)
- else:
- # Translators: this object is now unselected.
- # Let the user know this.
- #
- #
- speech.speak(C_("text", "unselected"), None, False)
- return
-
- if event.type.startswith("object:state-changed:focused"):
- iconified = False
- try:
- window = self.getTopLevel(event.source)
- iconified = window.getState().contains(pyatspi.STATE_ICONIFIED)
- except:
- debug.println(debug.LEVEL_FINEST,
- "onStateChanged: could not get frame of focused item")
- if not iconified:
- if event.detail1:
- self.onFocus(event)
- # We don't set locus of focus of None here because it
- # wreaks havoc on the code that determines the context
- # when you tab from widget to widget. For example,
- # tabbing between panels in the gtk-demo buttons demo.
- #
- #else:
- # orca.setLocusOfFocus(event, None)
- return
-
- # Handle tooltip popups.
- #
- if event.source.getRole() == pyatspi.ROLE_TOOL_TIP:
- obj = event.source
- if event.type.startswith("object:state-changed:showing"):
- if event.detail1 == 1:
- self.presentToolTip(obj)
- elif orca_state.locusOfFocus \
- and isinstance(orca_state.lastInputEvent,
- input_event.KeyboardEvent) \
- and (orca_state.lastNonModifierKeyEvent.event_string \
- == "F1"):
- self.updateBraille(orca_state.locusOfFocus)
- utterances = self.speechGenerator.generateSpeech(
- orca_state.locusOfFocus)
- utterances.extend(self.tutorialGenerator.getTutorial(
- orca_state.locusOfFocus, False))
- speech.speak(utterances)
- return
-
- if event.source.getRole() in state_change_notifiers:
- notifiers = state_change_notifiers[event.source.getRole()]
- found = False
- for state in notifiers:
- if state and event.type.endswith(state):
- found = True
- break
- if found:
- orca.visualAppearanceChanged(event, event.source)
-
- # [[[TODO: WDW - HACK we'll handle this in the visual appearance
- # changed handler.]]]
- #
- # The object with focus might become insensitive, so we need to
- # flag that. This typically occurs in wizard dialogs such as
- # the account setup assistant in Evolution.
- #
- #if event.type.endswith("sensitive") \
- # and (event.detail1 == 0) \
- # and event.source == orca_state.locusOfFocus:
- # print "FOO INSENSITIVE"
- # #orca.setLocusOfFocus(event, None)
-
- def getOffsetsForPhrase(self, obj):
- """Return the start and end offset for the given phrase
-
- Arguments:
- - obj: the Accessible object
- """
-
- text = obj.queryText()
- lastPos = self.pointOfReference.get("lastCursorPosition")
- startOffset = lastPos[1]
- endOffset = text.caretOffset
-
- # Swap values if in wrong order (StarOffice is fussy about that).
- #
- if ((startOffset > endOffset) and (endOffset != -1)) or \
- (startOffset == -1):
- temp = endOffset
- endOffset = startOffset
- startOffset = temp
- return [startOffset, endOffset]
-
- def getOffsetsForLine(self, obj):
- """Return the start and end offset for the given line
-
- Arguments:
- - obj: the Accessible object
- """
-
- [line, endOffset, startOffset] = self.getTextLineAtCaret(obj)
- return [startOffset, endOffset]
-
- def getOffsetsForWord(self, obj):
- """Return the start and end offset for the given word
-
- Arguments:
- - obj: the Accessible object
- """
-
- text = obj.queryText()
- offset = text.caretOffset
- [word, startOffset, endOffset] = text.getTextAtOffset(offset,
- pyatspi.TEXT_BOUNDARY_WORD_START)
- return [startOffset, endOffset]
-
- def getOffsetsForChar(self, obj):
- """Return the start and end offset for the given character
-
- Arguments:
- - obj: the Accessible object
- """
-
- text = obj.queryText()
- offset = text.caretOffset
-
- mods = orca_state.lastInputEvent.modifiers
- if (mods & settings.SHIFT_MODIFIER_MASK) \
- and orca_state.lastNonModifierKeyEvent.event_string == "Right":
- startOffset = offset-1
- endOffset = offset
- else:
- startOffset = offset
- endOffset = offset+1
-
- return [startOffset, endOffset]
-
- def onTextAttributesChanged(self, event):
- """Called when an object's text attributes change. Right now this
- method is only to handle the presentation of spelling errors on
- the fly. Also note that right now, the Gecko toolkit is the only
- one to present this information to us.
-
- Arguments:
- - event: the Event
- """
-
- if settings.speechVerbosityLevel == settings.VERBOSITY_LEVEL_VERBOSE \
- and self.isSameObject(event.source, orca_state.locusOfFocus):
- try:
- text = event.source.queryText()
- except:
- return
-
- # If the misspelled word indicator has just appeared, it's
- # because the user typed a word boundary or navigated out
- # of the word. We don't want to have to store a full set of
- # each object's text attributes to compare, therefore, we'll
- # check the previous word (most likely case) and the next
- # word with respect to the current position.
- #
- prevWordAndOffsets = \
- text.getTextAtOffset(text.caretOffset - 1,
- pyatspi.TEXT_BOUNDARY_WORD_START)
- nextWordAndOffsets = \
- text.getTextAtOffset(text.caretOffset + 1,
- pyatspi.TEXT_BOUNDARY_WORD_START)
-
- if self.isWordMisspelled(event.source, prevWordAndOffsets[1] ) \
- or self.isWordMisspelled(event.source, nextWordAndOffsets[1]):
- # Translators: this is to inform the user of the presence
- # of the red squiggly line which indicates that a given
- # word is not spelled correctly.
- #
- speech.speak(_("misspelled"))
-
def onTextSelectionChanged(self, event):
"""Called when an object's text selection changes.
@@ -4228,7 +4187,7 @@ class Script(script.Script):
for relation in lastPos[0].getRelationSet():
if relation.getRelationType() in [pyatspi.RELATION_FLOWS_FROM,
pyatspi.RELATION_FLOWS_TO] \
- and self.isSameObject(obj, relation.getTarget(0)):
+ and self.utilities.isSameObject(obj, relation.getTarget(0)):
relationType = relation.getRelationType()
break
@@ -4256,49 +4215,6 @@ class Script(script.Script):
self.speakTextSelectionState(obj, startOffset, endOffset)
- def onSelectionChanged(self, event):
- """Called when an object's selection changes.
-
- Arguments:
- - event: the Event
- """
-
- if not event or not event.source:
- return
-
- # Save the event source, if it is a menu or combo box. It will be
- # useful for optimizing getComponentAtDesktopCoords in the case
- # that the pointer is hovering over a menu item. The alternative is
- # to traverse the application's tree looking for potential moused-over
- # menu items.
- if event.source.getRole() in (pyatspi.ROLE_COMBO_BOX,
- pyatspi.ROLE_MENU):
- self.lastSelectedMenu = event.source
-
- # Avoid doing this with objects that manage their descendants
- # because they'll issue a descendant changed event.
- #
- if event.source.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS):
- return
-
- if event.source.getRole() == pyatspi.ROLE_COMBO_BOX:
- orca.visualAppearanceChanged(event, event.source)
-
- # We treat selected children as the locus of focus. When the
- # selection changed we want to update the locus of focus. If
- # there is no selection, we default the locus of focus to the
- # containing object.
- #
- elif (event.source != orca_state.locusOfFocus) and \
- event.source.getState().contains(pyatspi.STATE_FOCUSED):
- newFocus = event.source
- if event.source.childCount:
- selection = event.source.querySelection()
- if selection.nSelectedChildren > 0:
- newFocus = selection.getSelectedChild(0)
-
- orca.setLocusOfFocus(event, newFocus)
-
def onValueChanged(self, event):
"""Called whenever an object's value changes. Currently, the
value changes for non-focused objects are ignored.
@@ -4394,180 +4310,544 @@ class Script(script.Script):
orca.setLocusOfFocus(event, None)
orca_state.activeWindow = None
- def onMouseButton(self, event):
- """Called whenever the user presses or releases a mouse button.
+ ########################################################################
+ # #
+ # Methods for presenting content #
+ # #
+ ########################################################################
+
+ def _presentTextAtNewCaretPosition(self, event, otherObj=None):
+ """Updates braille, magnification, and outputs speech for the
+ event.source or the otherObj."""
+
+ obj = otherObj or event.source
+ text = obj.queryText()
+
+ if obj:
+ mag.magnifyAccessible(event, obj)
+
+ # Update the Braille display - if we can just reposition
+ # the cursor, then go for it.
+ #
+ brailleNeedsRepainting = True
+ line = braille.getShowingLine()
+ for region in line.regions:
+ if isinstance(region, braille.Text) \
+ and (region.accessible == obj):
+ if region.repositionCursor():
+ self.refreshBraille(True)
+ brailleNeedsRepainting = False
+ break
+
+ if brailleNeedsRepainting:
+ self.updateBraille(obj)
+
+ if not orca_state.lastInputEvent:
+ return
+
+ if isinstance(orca_state.lastInputEvent, input_event.MouseButtonEvent):
+ if not orca_state.lastInputEvent.pressed:
+ self.sayLine(obj)
+ return
+
+ # Guess why the caret moved and say something appropriate.
+ # [[[TODO: WDW - this motion assumes traditional GUI
+ # navigation gestures. In an editor such as vi, line up and
+ # down is done via other actions such as "i" or "j". We may
+ # need to think about this a little harder.]]]
+ #
+ if not isinstance(orca_state.lastInputEvent,
+ input_event.KeyboardEvent):
+ return
+
+ keyString = orca_state.lastNonModifierKeyEvent.event_string
+ mods = orca_state.lastInputEvent.modifiers
+ isControlKey = mods & settings.CTRL_MODIFIER_MASK
+ isShiftKey = mods & settings.SHIFT_MODIFIER_MASK
+ lastPos = self.pointOfReference.get("lastCursorPosition")
+ hasLastPos = (lastPos != None)
+
+ if (keyString == "Up") or (keyString == "Down"):
+ # If the user has typed Shift-Up or Shift-Down, then we want
+ # to speak the text that has just been selected or unselected,
+ # otherwise we speak the new line where the text cursor is
+ # currently positioned.
+ #
+ if hasLastPos and isShiftKey and not isControlKey:
+ if keyString == "Up":
+ # If we have just crossed a paragraph boundary with
+ # 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.utilities.isSameObject(lastPos[0], obj):
+ [startOffset, endOffset] = \
+ text.caretOffset, text.characterCount
+ else:
+ [startOffset, endOffset] = \
+ self.utilities.offsetsForPhrase(obj)
+ self.sayPhrase(obj, startOffset, endOffset)
+ selSpoken = self._speakContiguousSelection(obj,
+ pyatspi.RELATION_FLOWS_TO)
+ else:
+ selSpoken = self._speakContiguousSelection(obj,
+ pyatspi.RELATION_FLOWS_FROM)
+
+ # If we have just crossed a paragraph boundary with
+ # Shift+Down, what we've selected in this object starts
+ # with the beginning of the paragraph and goes to the
+ # current offset.
+ #
+ if not self.utilities.isSameObject(lastPos[0], obj):
+ [startOffset, endOffset] = 0, text.caretOffset
+ else:
+ [startOffset, endOffset] \
+ = self.utilities.offsetsForPhrase(obj)
+
+ if startOffset != endOffset:
+ self.sayPhrase(obj, startOffset, endOffset)
+
+ else:
+ [startOffset, endOffset] = self.utilities.offsetsForLine(obj)
+ self.sayLine(obj)
+
+ elif (keyString == "Left") or (keyString == "Right"):
+ # If the user has typed Control-Shift-Up or Control-Shift-Dowm,
+ # then we want to speak the text that has just been selected
+ # or unselected, otherwise if the user has typed Control-Left
+ # or Control-Right, we speak the current word otherwise we speak
+ # the character at the text cursor position.
+ #
+ inNewObj = hasLastPos \
+ and not self.utilities.isSameObject(lastPos[0], obj)
+
+ if hasLastPos and not inNewObj and isShiftKey and isControlKey:
+ [startOffset, endOffset] = self.utilities.offsetsForPhrase(obj)
+ self.sayPhrase(obj, startOffset, endOffset)
+ elif isControlKey and not isShiftKey:
+ [startOffset, endOffset] = self.utilities.offsetsForWord(obj)
+ if startOffset == endOffset:
+ self.sayCharacter(obj)
+ else:
+ self.sayWord(obj)
+ else:
+ [startOffset, endOffset] = self.utilities.offsetsForChar(obj)
+ self.sayCharacter(obj)
+
+ elif keyString == "Page_Up":
+ # If the user has typed Control-Shift-Page_Up, then we want
+ # to speak the text that has just been selected or unselected,
+ # otherwise if the user has typed Control-Page_Up, then we
+ # speak the character to the right of the current text cursor
+ # position otherwise we speak the current line.
+ #
+ if hasLastPos and isShiftKey and isControlKey:
+ [startOffset, endOffset] = self.utilities.offsetsForPhrase(obj)
+ self.sayPhrase(obj, startOffset, endOffset)
+ elif isControlKey:
+ [startOffset, endOffset] = self.utilities.offsetsForChar(obj)
+ self.sayCharacter(obj)
+ else:
+ [startOffset, endOffset] = self.utilities.offsetsForLine(obj)
+ self.sayLine(obj)
+
+ elif keyString == "Page_Down":
+ # If the user has typed Control-Shift-Page_Down, then we want
+ # to speak the text that has just been selected or unselected,
+ # otherwise if the user has just typed Page_Down, then we speak
+ # the current line.
+ #
+ if hasLastPos and isShiftKey and isControlKey:
+ [startOffset, endOffset] = self.utilities.offsetsForPhrase(obj)
+ self.sayPhrase(obj, startOffset, endOffset)
+ else:
+ [startOffset, endOffset] = self.utilities.offsetsForLine(obj)
+ self.sayLine(obj)
+
+ elif (keyString == "Home") or (keyString == "End"):
+ # If the user has typed Shift-Home or Shift-End, then we want
+ # to speak the text that has just been selected or unselected,
+ # otherwise if the user has typed Control-Home or Control-End,
+ # then we speak the current line otherwise we speak the character
+ # to the right of the current text cursor position.
+ #
+ if hasLastPos and isShiftKey and not isControlKey:
+ [startOffset, endOffset] = self.utilities.offsetsForPhrase(obj)
+ self.sayPhrase(obj, startOffset, endOffset)
+ elif isControlKey:
+ [startOffset, endOffset] = self.utilities.offsetsForLine(obj)
+ self.sayLine(obj)
+ else:
+ [startOffset, endOffset] = self.utilities.offsetsForChar(obj)
+ self.sayCharacter(obj)
+
+ else:
+ startOffset = text.caretOffset
+ endOffset = text.caretOffset
+
+ self._saveLastCursorPosition(obj, text.caretOffset)
+ self._saveSpokenTextRange(startOffset, endOffset)
+
+ def __sayAllProgressCallback(self, context, progressType):
+ # [[[TODO: WDW - this needs work. Need to be able to manage
+ # the monitoring of progress and couple that with both updating
+ # the visual progress of what is being spoken as well as
+ # positioning the cursor when speech has stopped.]]]
+ #
+ text = context.obj.queryText()
+ if progressType == speechserver.SayAllContext.PROGRESS:
+ #print "PROGRESS", context.utterance, context.currentOffset
+ #obj = context.obj
+ #[x, y, width, height] = obj.text.getCharacterExtents(
+ # context.currentOffset, 0)
+ #print context.currentOffset, x, y, width, height
+ #self.drawOutline(x, y, width, height)
+ return
+ elif progressType == speechserver.SayAllContext.INTERRUPTED:
+ #print "INTERRUPTED", context.utterance, context.currentOffset
+ text.setCaretOffset(context.currentOffset)
+ elif progressType == speechserver.SayAllContext.COMPLETED:
+ #print "COMPLETED", context.utterance, context.currentOffset
+ orca.setLocusOfFocus(
+ None, context.obj, notifyPresentationManager=False)
+ text.setCaretOffset(context.currentOffset)
+
+ # If there is a selection, clear it. See bug #489504 for more details.
+ #
+ if text.getNSelections():
+ text.setSelection(0, context.currentOffset, context.currentOffset)
+
+ 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
+ with a RELATION_FLOWS_FROM relationship. If they pressed Shift-Up,
+ then look for a RELATION_FLOWS_TO relationship.
Arguments:
- - event: the Event
+ - the current text object
+ - the flows relationship (RELATION_FLOWS_FROM or RELATION_FLOWS_TO).
+
+ Returns an indication of whether anything was spoken.
"""
- # If we've received a mouse button released event, then check if
- # there are and text selections for the locus of focus and speak
- # them.
+ lastPos = self.pointOfReference.get("lastCursorPosition")
+
+ # Reasons to NOT speak contiguous selections:
#
- state = event.type[-1]
- if state == "r":
- obj = orca_state.locusOfFocus
- try:
- text = obj.queryText()
- except:
- pass
- else:
- [textContents, startOffset, endOffset] = \
- self.getAllSelectedText(obj)
- if textContents:
- utterances = []
- utterances.append(textContents)
+ # 1. The new cursor position is in the same object as the old
+ # cursor position. (The change in selection is all within
+ # the current object.)
+ # 2. If we are selecting up line by line from the beginning of
+ # the line and have just crossed into a new object, the change
+ # in selection is the previous line (which has just become
+ # selected). Nothing has changed on the line we came from.
+ #
+ if self.utilities.isSameObject(lastPos[0], obj) \
+ or relationship == pyatspi.RELATION_FLOWS_TO and lastPos[1] == 0:
+ return False
- # Translators: when the user selects (highlights) text in
- # a document, Orca lets them know this.
+ selSpoken = False
+ current = obj
+ for relation in current.getRelationSet():
+ if relation.getRelationType() == relationship:
+ obj = relation.getTarget(0)
+ objText = obj.queryText()
+
+ # 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
+ # the line.
+ #
+ if relationship == pyatspi.RELATION_FLOWS_FROM:
+ start, end = lastPos[1], objText.characterCount
+
+ # When selecting up across paragraph boundaries, what
+ # we've (un)selected on (what is now) the next line is
+ # from the beginning of the line to wherever the cursor
+ # used to be.
+ #
+ else:
+ start, end = 0, lastPos[1]
+
+ if objText.getNSelections() > 0:
+ [textContents, startOffset, endOffset] = \
+ self.utilities.selectedText(obj)
+
+ # Now that we have the full selection, adjust based
+ # on the relation type. (see above comment)
#
- utterances.append(C_("text", "selected"))
- speech.speak(utterances)
- self.updateBraille(orca_state.locusOfFocus)
+ startOffset = start or startOffset
+ endOffset = end or endOffset
+ self.sayPhrase(obj, startOffset, endOffset)
+ selSpoken = True
+ else:
+ # We don't have selections in this object. But we're
+ # here, which means that something is selected in a
+ # neighboring object and the text in this object must
+ # have just become unselected and needs to be spoken.
+ #
+ self.sayPhrase(obj, start, end)
+ selSpoken = True
- def noOp(self, event):
- """Just here to capture events.
+ return selSpoken
+
+ def echoPreviousSentence(self, obj):
+ """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:
- - event: the Event
+ - obj: an Accessible object that implements the AccessibleText
+ interface.
"""
- pass
- ########################################################################
- # #
- # Utilities #
- # #
- ########################################################################
+ try:
+ text = obj.queryText()
+ except NotImplementedError:
+ return
+
+ offset = text.caretOffset - 1
+ previousOffset = text.caretOffset - 2
+ if (offset < 0 or previousOffset < 0):
+ return
- def isLayoutOnly(self, obj):
- """Returns True if the given object is a table and is for layout
- purposes only."""
+ [currentChar, startOffset, endOffset] = \
+ text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR)
+ [previousChar, startOffset, endOffset] = \
+ text.getTextAtOffset(previousOffset, pyatspi.TEXT_BOUNDARY_CHAR)
+ if not self.utilities.isSentenceDelimiter(currentChar, previousChar):
+ return
- layoutOnly = False
+ # 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
+ # we hit another sentence delimiter.
+ #
+ sentenceEndOffset = text.caretOffset - 2
+ sentenceStartOffset = sentenceEndOffset
- if obj:
- attributes = obj.getAttributes()
+ while sentenceStartOffset >= 0:
+ [currentChar, startOffset, endOffset] = \
+ text.getTextAtOffset(sentenceStartOffset,
+ pyatspi.TEXT_BOUNDARY_CHAR)
+ [previousChar, startOffset, endOffset] = \
+ text.getTextAtOffset(sentenceStartOffset-1,
+ pyatspi.TEXT_BOUNDARY_CHAR)
+ if self.utilities.isSentenceDelimiter(currentChar, previousChar):
+ break
+ else:
+ sentenceStartOffset -= 1
+
+ # If we came across a sentence delimiter before hitting any
+ # text, we really don't have a previous sentence.
+ #
+ # Otherwise, get the sentence. Remember we stopped when we
+ # hit a sentence delimiter, so the sentence really starts at
+ # sentenceStartOffset + 1. getText also does not include
+ # the character at sentenceEndOffset, so we need to adjust
+ # for that, too.
+ #
+ if sentenceStartOffset == sentenceEndOffset:
+ return
else:
- attributes = None
+ sentence = self.utilities.substring(obj, sentenceStartOffset + 1,
+ sentenceEndOffset + 1)
- if obj and (obj.getRole() == pyatspi.ROLE_TABLE) and attributes:
- for attribute in attributes:
- if attribute == "layout-guess:true":
- layoutOnly = True
- break
- elif obj and (obj.getRole() == pyatspi.ROLE_PANEL):
- text = self.getDisplayedText(obj)
- label = self.getDisplayedLabel(obj)
- if not ((label and len(label)) or (text and len(text))):
- layoutOnly = True
+ if self.utilities.linkIndex(obj, sentenceStartOffset + 1) >= 0:
+ voice = self.voices[settings.HYPERLINK_VOICE]
+ elif sentence.decode("UTF-8").isupper():
+ voice = self.voices[settings.UPPERCASE_VOICE]
+ else:
+ voice = self.voices[settings.DEFAULT_VOICE]
- if layoutOnly:
- debug.println(debug.LEVEL_FINEST,
- "Object deemed to be for layout purposes only: %s" \
- % obj)
+ sentence = self.utilities.adjustForRepeats(sentence)
+ speech.speak(sentence, voice)
- return layoutOnly
+ 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
+ delimiter between the caret and the end of the word.
- def toggleSpeakingIndentationJustification(self, inputEvent=None):
- """Toggles the speaking of indentation and justification."""
+ The entry condition for this method is that the character
+ prior to the current caret position is a word delimiter,
+ and it's what caused this method to be called in the first
+ place.
- settings.enableSpeechIndentation = not settings.enableSpeechIndentation
- if settings.enableSpeechIndentation :
- # Translators: A message indicating that
- # indentation and justification will be spoken.
- #
- line = _("Speaking of indentation and justification enabled.")
- else:
- # Translators: A message indicating that
- # indentation and justification will not be spoken.
- #
- line = _("Speaking of indentation and justification disabled.")
+ Arguments:
+ - obj: an Accessible object that implements the AccessibleText
+ interface.
+ - offset: if not None, the offset within the text to use as the
+ end of the word.
+ """
- speech.speak(line)
+ try:
+ text = obj.queryText()
+ except NotImplementedError:
+ return
- return True
+ if not offset:
+ offset = text.caretOffset - 1
+ if (offset < 0):
+ return
- def toggleTableCellReadMode(self, inputEvent=None):
- """Toggles an indicator for whether we should just read the current
- table cell or read the whole row."""
+ [char, startOffset, endOffset] = \
+ text.getTextAtOffset( \
+ offset,
+ pyatspi.TEXT_BOUNDARY_CHAR)
+ if not self.utilities.isWordDelimiter(char):
+ return
- settings.readTableCellRow = not settings.readTableCellRow
- if settings.readTableCellRow:
- # Translators: when users are navigating a table, they
- # sometimes want the entire row of a table read, or
- # they just want the current cell to be presented to them.
- #
- line = _("Speak row")
+ # OK - we seem to be cool so far. So...starting with what
+ # should be the last character in the word (caretOffset - 2),
+ # work our way to the beginning of the word, stopping when
+ # we hit another word delimiter.
+ #
+ wordEndOffset = offset - 1
+ wordStartOffset = wordEndOffset
+
+ while wordStartOffset >= 0:
+ [char, startOffset, endOffset] = \
+ text.getTextAtOffset( \
+ wordStartOffset,
+ pyatspi.TEXT_BOUNDARY_CHAR)
+ if self.utilities.isWordDelimiter(char):
+ break
+ else:
+ wordStartOffset -= 1
+
+ # If we came across a word delimiter before hitting any
+ # text, we really don't have a previous word.
+ #
+ # Otherwise, get the word. Remember we stopped when we
+ # hit a word delimiter, so the word really starts at
+ # wordStartOffset + 1. getText also does not include
+ # the character at wordEndOffset, so we need to adjust
+ # for that, too.
+ #
+ if wordStartOffset == wordEndOffset:
+ return
else:
- # Translators: when users are navigating a table, they
- # sometimes want the entire row of a table read, or
- # they just want the current cell to be presented to them.
- #
- line = _("Speak cell")
+ word = self.utilities.\
+ substring(obj, wordStartOffset + 1, wordEndOffset + 1)
- speech.speak(line)
+ if self.utilities.linkIndex(obj, wordStartOffset + 1) >= 0:
+ voice = self.voices[settings.HYPERLINK_VOICE]
+ elif word.decode("UTF-8").isupper():
+ voice = self.voices[settings.UPPERCASE_VOICE]
+ else:
+ voice = self.voices[settings.DEFAULT_VOICE]
- return True
+ word = self.utilities.adjustForRepeats(word)
+ speech.speak(word, voice)
- def getAtkNameForAttribute(self, attribName):
- """Converts the given attribute name into the Atk equivalent. This
- is necessary because an application or toolkit (e.g. Gecko) might
- invent entirely new names for the same attributes.
+ def handleProgressBarUpdate(self, event, obj):
+ """Determine whether this progress bar event should be spoken or not.
+ It should be spoken if:
+ 1/ settings.enableProgressBarUpdates is True.
+ 2/ settings.progressBarVerbosity matches the current location of the
+ progress bar.
+ 3/ The time of this event exceeds the
+ settings.progressBarUpdateInterval value. This value
+ indicates the time (in seconds) between potential spoken
+ progress bar updates.
+ 4/ The new value of the progress bar (converted to an integer),
+ is different from the last one or equals 100 (i.e complete).
Arguments:
- - attribName: The name of the text attribute
-
- Returns the Atk equivalent name if found or attribName otherwise.
+ - event: if not None, the Event that caused this to happen
+ - obj: the Accessible progress bar object.
"""
- return self.attributeNamesDict.get(attribName, attribName)
+ if settings.enableProgressBarUpdates:
+ makeAnnouncment = False
+ if settings.progressBarVerbosity == settings.PROGRESS_BAR_ALL:
+ makeAnnouncement = True
+ elif settings.progressBarVerbosity == settings.PROGRESS_BAR_WINDOW:
+ makeAnnouncement = self.utilities.isSameObject(
+ self.utilities.topLevelObject(obj),
+ self.utilities.activeWindow())
+ elif orca_state.locusOfFocus:
+ makeAnnouncement = self.utilities.isSameObject( \
+ obj.getApplication(),
+ orca_state.locusOfFocus.getApplication())
- def getAppNameForAttribute(self, attribName):
- """Converts the given Atk attribute name into the application's
- equivalent. This is necessary because an application or toolkit
- (e.g. Gecko) might invent entirely new names for the same text
- attributes.
+ if makeAnnouncement:
+ currentTime = time.time()
- Arguments:
- - attribName: The name of the text attribute
+ # Check for defunct progress bars. Get rid of them if they
+ # are all defunct. Also find out which progress bar was
+ # the most recently updated.
+ #
+ defunctBars = 0
+ mostRecentUpdate = [obj, 0]
+ for key, value in self.lastProgressBarTime.items():
+ if value > mostRecentUpdate[1]:
+ mostRecentUpdate = [key, value]
+ try:
+ isDefunct = \
+ key.getState().contains(pyatspi.STATE_DEFUNCT)
+ except:
+ isDefunct = True
+ if isDefunct:
+ defunctBars += 1
- Returns the application's equivalent name if found or attribName
- otherwise.
- """
+ if defunctBars == len(self.lastProgressBarTime):
+ self.lastProgressBarTime = {}
+ self.lastProgressBarValue = {}
- for key, value in self.attributeNamesDict.items():
- if value == attribName:
- return key
+ # If this progress bar is not already known, create initial
+ # values for it.
+ #
+ if obj not in self.lastProgressBarTime:
+ self.lastProgressBarTime[obj] = 0.0
+ if obj not in self.lastProgressBarValue:
+ self.lastProgressBarValue[obj] = None
- return attribName
+ lastProgressBarTime = self.lastProgressBarTime[obj]
+ lastProgressBarValue = self.lastProgressBarValue[obj]
+ value = obj.queryValue()
+ percentValue = int((value.currentValue / \
+ (value.maximumValue - value.minimumValue)) * 100.0)
- def textAttrsToDictionary(self, tokenString):
- """Converts a string of text attribute tokens of the form
- <key>:<value>; into a dictionary of keys and values.
- Text before the colon is the key and text afterwards is the
- value. If there is a final semi-colon, then it's ignored.
+ if (currentTime - lastProgressBarTime) > \
+ settings.progressBarUpdateInterval \
+ or percentValue == 100:
+ if lastProgressBarValue != percentValue:
+ utterances = []
- Arguments:
- - tokenString: the string of tokens containing <key>:<value>; pairs.
+ # There may be cases when more than one progress
+ # bar is updating at the same time in a window.
+ # If this is the case, then speak the index of this
+ # progress bar in the dictionary of known progress
+ # bars, as well as the value. But only speak the
+ # index if this progress bar was not the most
+ # recently updated to prevent chattiness.
+ #
+ if len(self.lastProgressBarTime) > 1:
+ index = 0
+ for key in self.lastProgressBarTime.keys():
+ if key == obj and key != mostRecentUpdate[0]:
+ # Translators: this is an index value
+ # so that we can tell which progress bar
+ # we are referring to.
+ #
+ label = _("Progress bar %d.") % (index + 1)
+ utterances.append(label)
+ else:
+ index += 1
- Returns a list containing two items:
- A list of the keys in the order they were extracted from the
- text attribute string and a dictionary of key/value items.
- """
+ utterances.extend(self.speechGenerator.generateSpeech(
+ obj, alreadyFocused=True))
- keyList = []
- dictionary = {}
- allTokens = tokenString.split(";")
- for token in allTokens:
- item = token.split(":")
- if len(item) == 2:
- key = item[0].strip()
- attribute = item[1].strip()
- keyList.append(key)
- dictionary[key] = attribute
+ speech.speak(utterances)
- return [keyList, dictionary]
+ self.lastProgressBarTime[obj] = currentTime
+ self.lastProgressBarValue[obj] = percentValue
def outputCharAttributes(self, keys, attributes):
"""Speak each of the text attributes given dictionary.
@@ -4637,359 +4917,292 @@ class Script(script.Script):
line = line or (localizedKey + " " + localizedValue)
speech.speak(line)
- def readCharAttributes(self, inputEvent=None):
- """Reads the attributes associated with the current text character.
- Calls outCharAttributes to speak a list of attributes. By default,
- a certain set of attributes will be spoken. If this is not desired,
- then individual application scripts should override this method to
- only speak the subset required.
+ def presentToolTip(self, obj):
+ """
+ Speaks the tooltip for the current object of interest.
"""
- try:
- text = orca_state.locusOfFocus.queryText()
- except:
- pass
+ # The tooltip is generally the accessible description. If
+ # the description is not set, present the text that is
+ # spoken when the object receives keyboard focus.
+ #
+ text = ""
+ if obj.description:
+ speechResult = brailleResult = obj.description
else:
- caretOffset = text.caretOffset
+ speechResult = self.whereAmI.getWhereAmI(obj, True)
+ brailleResult = speechResult[0]
+ debug.println(debug.LEVEL_FINEST,
+ "presentToolTip: text='%s'" % speechResult)
+ if speechResult:
+ speech.speak(speechResult)
+ if brailleResult:
+ self.displayBrailleMessage(brailleResult)
- # Creates dictionaries of the default attributes, plus the set
- # of attributes specific to the character at the caret offset.
- # Combine these two dictionaries and then extract just the
- # entries we are interested in.
- #
- defAttributes = text.getDefaultAttributes()
- debug.println(debug.LEVEL_FINEST, \
- "readCharAttributes: default text attributes: %s" % \
- defAttributes)
- [defUser, defDict] = self.textAttrsToDictionary(defAttributes)
- allAttributes = defDict
+ def sayCharacter(self, obj):
+ """Speak the character at the caret.
- charAttributes = text.getAttributes(caretOffset)
- if charAttributes[0]:
- [charList, charDict] = \
- self.textAttrsToDictionary(charAttributes[0])
+ Arguments:
+ - obj: an Accessible object that implements the AccessibleText
+ interface
+ """
- # It looks like some applications like Evolution and Star
- # Office don't implement getDefaultAttributes(). In that
- # case, the best we can do is use the specific text
- # attributes for this character returned by getAttributes().
- #
- if allAttributes:
- for key in charDict.keys():
- allAttributes[key] = charDict[key]
- else:
- allAttributes = charDict
+ text = obj.queryText()
+ offset = text.caretOffset
- # Get a dictionary of text attributes that the user cares about.
- #
- [userAttrList, userAttrDict] = \
- self.textAttrsToDictionary(settings.enabledSpokenTextAttributes)
+ # If we have selected text and the last event was a move to the
+ # right, then speak the character to the left of where the text
+ # caret is (i.e. the selected character).
+ #
+ try:
+ mods = orca_state.lastInputEvent.modifiers
+ eventString = orca_state.lastInputEvent.event_string
+ except:
+ mods = 0
+ eventString = ""
- # Create a dictionary of just the items we are interested in.
- # Always include size and family-name. For the others, if the
- # value is the default, then ignore it.
- #
- attributes = {}
- for key in userAttrList:
- if key in allAttributes:
- textAttr = allAttributes.get(key)
- userAttr = userAttrDict.get(key)
- if textAttr != userAttr or len(userAttr) == 0:
- attributes[key] = textAttr
+ if (mods & settings.SHIFT_MODIFIER_MASK) \
+ and eventString in ["Right", "Down"]:
+ offset -= 1
- self.outputCharAttributes(userAttrList, attributes)
+ character, startOffset, endOffset = \
+ text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR)
+ if not character:
+ character = "\n"
- # If this is a hypertext link, then let the user know:
- #
- if self.getLinkIndex(orca_state.locusOfFocus, caretOffset) >= 0:
- # Translators: this indicates that this piece of
- # text is a hypertext link.
- #
- speech.speak(_("link"))
+ if self.utilities.linkIndex(obj, offset) >= 0:
+ voice = self.voices[settings.HYPERLINK_VOICE]
+ elif character.decode("UTF-8").isupper():
+ voice = self.voices[settings.UPPERCASE_VOICE]
+ else:
+ voice = self.voices[settings.DEFAULT_VOICE]
- return True
+ debug.println(debug.LEVEL_FINEST, \
+ "sayCharacter: char=<%s>, startOffset=%d, " % \
+ (character, startOffset))
+ debug.println(debug.LEVEL_FINEST, \
+ "caretOffset=%d, endOffset=%d, speakBlankLines=%s" % \
+ (offset, endOffset, settings.speakBlankLines))
- def hasTextSelections(self, obj):
- """Return an indication of whether this object has selected text.
- Note that it's possible that this object has no selection, but is part
- of a selected text area. Because of this, we need to check the
- objects on either side to see if they are none zero length and
- have text selections.
+ if character == "\n":
+ line = text.getTextAtOffset(max(0, offset),
+ pyatspi.TEXT_BOUNDARY_LINE_START)
+ if not line[0] or line[0] == "\n":
+ # This is a blank line. Announce it if the user requested
+ # that blank lines be spoken.
+ if settings.speakBlankLines:
+ # Translators: "blank" is a short word to mean the
+ # user has navigated to an empty line.
+ #
+ speech.speak(_("blank"), voice, False)
+ return
- Arguments:
- - obj: the text object to start checking for selected text.
+ self.speakMisspelledIndicator(obj, offset)
+ speech.speakCharacter(character, voice)
+
+ def sayLine(self, obj):
+ """Speaks the line of an AccessibleText object that contains the
+ caret, unless the line is empty in which case it's ignored.
- Returns: an indication of whether this object has selected text,
- or adjacent text objects have selected text.
+ Arguments:
+ - obj: an Accessible object that implements the AccessibleText
+ interface
"""
- currentSelected = False
- otherSelected = False
- text = obj.queryText()
- nSelections = text.getNSelections()
- if nSelections:
- currentSelected = True
- else:
- otherSelected = False
- text = obj.queryText()
- displayedText = text.getText(0, self.getTextEndOffset(text))
- if (text.caretOffset == 0) or len(displayedText) == 0:
- current = obj
- morePossibleSelections = True
- while morePossibleSelections:
- morePossibleSelections = False
- for relation in current.getRelationSet():
- if relation.getRelationType() == \
- pyatspi.RELATION_FLOWS_FROM:
- prevObj = relation.getTarget(0)
- prevObjText = prevObj.queryText()
- if prevObjText.getNSelections() > 0:
- otherSelected = True
- else:
- displayedText = prevObjText.getText(0,
- self.getTextEndOffset(prevObjText))
- if len(displayedText) == 0:
- current = prevObj
- morePossibleSelections = True
- break
+ # Get the AccessibleText interface of the provided object
+ #
+ [line, caretOffset, startOffset] = self.getTextLineAtCaret(obj)
+ debug.println(debug.LEVEL_FINEST, \
+ "sayLine: line=<%s>, len=%d, start=%d, " % \
+ (line, len(line), startOffset))
+ debug.println(debug.LEVEL_FINEST, \
+ "caret=%d, speakBlankLines=%s" % \
+ (caretOffset, settings.speakBlankLines))
- current = obj
- morePossibleSelections = True
- while morePossibleSelections:
- morePossibleSelections = False
- for relation in current.getRelationSet():
- if relation.getRelationType() == \
- pyatspi.RELATION_FLOWS_TO:
- nextObj = relation.getTarget(0)
- nextObjText = nextObj.queryText()
- if nextObjText.getNSelections() > 0:
- otherSelected = True
- else:
- displayedText = nextObjText.getText(0,
- self.getTextEndOffset(nextObjText))
- if len(displayedText) == 0:
- current = nextObj
- morePossibleSelections = True
- break
+ if len(line) and line != "\n":
+ if line.decode("UTF-8").isupper():
+ voice = self.voices[settings.UPPERCASE_VOICE]
+ else:
+ voice = self.voices[settings.DEFAULT_VOICE]
- return [currentSelected, otherSelected]
+ if settings.enableSpeechIndentation:
+ self.speakTextIndentation(obj, line)
+ line = self.utilities.adjustForLinks(obj, line, startOffset)
+ line = self.utilities.adjustForRepeats(line)
+ speech.speak(line, voice)
+ else:
+ # Speak blank line if appropriate.
+ #
+ self.sayCharacter(obj)
- def reportScriptInfo(self, inputEvent=None):
- """Output useful information on the current script via speech
- and braille. This information will be helpful to script writers.
+ def sayPhrase(self, obj, startOffset, endOffset):
+ """Speaks the text of an Accessible object between the start and
+ end offsets, unless the phrase is empty in which case it's ignored.
+
+ Arguments:
+ - obj: an Accessible object that implements the AccessibleText
+ interface
+ - startOffset: the start text offset.
+ - endOffset: the end text offset.
"""
- infoString = "SCRIPT INFO: Script name='%s'" % self.name
- app = orca_state.locusOfFocus.getApplication()
- if orca_state.locusOfFocus and app:
- infoString += " Application name='%s'" \
- % app.name
+ phrase = self.utilities.substring(obj, startOffset, endOffset)
- try:
- infoString += " Toolkit name='%s'" \
- % app.toolkitName
- except:
- infoString += " Toolkit unknown"
+ if len(phrase) and phrase != "\n":
+ if phrase.decode("UTF-8").isupper():
+ voice = self.voices[settings.UPPERCASE_VOICE]
+ else:
+ voice = self.voices[settings.DEFAULT_VOICE]
- try:
- infoString += " Version='%s'" \
- % app.version
- except:
- infoString += " Version unknown"
+ phrase = self.utilities.adjustForRepeats(phrase)
+ speech.speak(phrase, voice)
+ else:
+ # Speak blank line if appropriate.
+ #
+ self.sayCharacter(obj)
- debug.println(debug.LEVEL_OFF, infoString)
- print infoString
- speech.speak(infoString)
- self.displayBrailleMessage(infoString)
+ def sayWord(self, obj):
+ """Speaks the word at the caret. [[[TODO: WDW - what if there is no
+ word at the caret?]]]
- return True
+ Arguments:
+ - obj: an Accessible object that implements the AccessibleText
+ interface
+ """
- def bypassNextCommand(self, inputEvent=None):
- """Causes the next keyboard command to be ignored by Orca
- and passed along to the current application.
+ text = obj.queryText()
+ offset = text.caretOffset
+ lastKey = orca_state.lastNonModifierKeyEvent.event_string
+ lastWord = orca_state.lastWord
- Returns True to indicate the input event has been consumed.
- """
+ [word, startOffset, endOffset] = \
+ text.getTextAtOffset(offset,
+ pyatspi.TEXT_BOUNDARY_WORD_START)
- # Translators: Orca normally intercepts all keyboard
- # commands and only passes them along to the current
- # application when they are not Orca commands. This
- # command causes the next command issued to be passed
- # along to the current application, bypassing Orca's
- # interception of it.
+ # Speak a newline if a control-right-arrow or control-left-arrow
+ # was used to cross a line boundary. Handling is different for
+ # the two keys since control-right-arrow places the cursor after
+ # the last character in a word, but control-left-arrow places
+ # the cursor at the beginning of a word.
#
- speech.speak(_("Bypass mode enabled."))
- orca_state.bypassNextCommand = True
- return True
+ if lastKey == "Right" and len(lastWord) > 0:
+ lastChar = lastWord[len(lastWord) - 1]
+ if lastChar == "\n" and lastWord != word:
+ voice = self.voices[settings.DEFAULT_VOICE]
+ speech.speakCharacter("\n", voice)
- def enterLearnMode(self, inputEvent=None):
- """Turns learn mode on. The user must press the escape key to exit
- learn mode.
+ if lastKey == "Left" and len(word) > 0:
+ lastChar = word[len(word) - 1]
+ if lastChar == "\n" and lastWord != word:
+ voice = self.voices[settings.DEFAULT_VOICE]
+ speech.speakCharacter("\n", voice)
- Returns True to indicate the input event has been consumed.
- """
+ if self.utilities.linkIndex(obj, offset) >= 0:
+ voice = self.voices[settings.HYPERLINK_VOICE]
+ elif word.decode("UTF-8").isupper():
+ voice = self.voices[settings.UPPERCASE_VOICE]
+ else:
+ voice = self.voices[settings.DEFAULT_VOICE]
- if settings.learnModeEnabled:
- return True
+ self.speakMisspelledIndicator(obj, startOffset)
- speech.speak(
- # Translators: Orca has a "Learn Mode" that will allow
- # the user to type any key on the keyboard and hear what
- # the effects of that key would be. The effects might
- # be what Orca would do if it had a handler for the
- # particular key combination, or they might just be to
- # echo the name of the key if Orca doesn't have a handler.
- # This text here is what is spoken to the user.
- #
- _("Entering learn mode. Press any key to hear its function. " \
- "To exit learn mode, press the escape key."))
+ word = self.utilities.adjustForRepeats(word)
+ orca_state.lastWord = word
+ speech.speak(word, voice)
- # Translators: Orca has a "Learn Mode" that will allow
- # the user to type any key on the keyboard and hear what
- # the effects of that key would be. The effects might
- # be what Orca would do if it had a handler for the
- # particular key combination, or they might just be to
- # echo the name of the key if Orca doesn't have a handler.
- # This text here is what is to be presented on the braille
- # display.
+ def speakTextIndentation(self, obj, line):
+ """Speaks a summary of the number of spaces and/or tabs at the
+ beginning of the given line.
+
+ Arguments:
+ - obj: the text object.
+ - line: the string to check for spaces and tabs.
+ """
+
+ # For the purpose of speaking the text indentation, replace
+ # occurances of UTF-8 '\302\240' (non breaking space) with
+ # spaces.
#
- self.displayBrailleMessage(_("Learn mode. Press escape to exit."))
- settings.learnModeEnabled = True
- return True
+ line = line.replace("\302\240", " ")
+ line = line.decode("UTF-8")
+
+ spaceCount = 0
+ tabCount = 0
+ utterance = ""
+ offset = 0
+ while True:
+ while (offset < len(line)) and line[offset] == ' ':
+ spaceCount += 1
+ offset += 1
+ if spaceCount:
+ # Translators: this is the number of space characters on a line
+ # of text.
+ #
+ utterance += ngettext("%d space",
+ "%d spaces",
+ spaceCount) % spaceCount + " "
+
+ while (offset < len(line)) and line[offset] == '\t':
+ tabCount += 1
+ offset += 1
+ if tabCount:
+ # Translators: this is the number of tab characters on a line
+ # of text.
+ #
+ utterance += ngettext("%d tab",
+ "%d tabs",
+ tabCount) % tabCount + " "
- def pursueForFlatReview(self, obj):
- """Determines if we should look any further at the object
- for flat review."""
+ if not (spaceCount or tabCount):
+ break
+ spaceCount = tabCount = 0
- try:
- state = obj.getState()
- except:
- debug.printException(debug.LEVEL_WARNING)
- return False
- else:
- return state.contains(pyatspi.STATE_SHOWING)
+ if len(utterance):
+ speech.speak(utterance)
- def getShowingDescendants(self, parent):
- """Given a parent that manages its descendants, return a list of
- Accessible children that are actually showing. This algorithm
- was inspired a little by the srw_elements_from_accessible logic
- in Gnopernicus, and makes the assumption that the children of
- an object that manages its descendants are arranged in a row
- and column format.
+ def stopSpeechOnActiveDescendantChanged(self, event):
+ """Whether or not speech should be stopped prior to setting the
+ locusOfFocus in onActiveDescendantChanged.
Arguments:
- - parent: The accessible which manages its descendants
+ - event: the Event
- Returns a list of Accessible descendants which are showing.
+ Returns True if speech should be stopped; False otherwise.
"""
- if not parent:
- return []
+ return True
- if not parent.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS) \
- or parent.childCount <= 50:
- return []
+ def getAtkNameForAttribute(self, attribName):
+ """Converts the given attribute name into the Atk equivalent. This
+ is necessary because an application or toolkit (e.g. Gecko) might
+ invent entirely new names for the same attributes.
- try:
- icomponent = parent.queryComponent()
- except NotImplementedError:
- return []
+ Arguments:
+ - attribName: The name of the text attribute
- descendants = []
+ Returns the Atk equivalent name if found or attribName otherwise.
+ """
- parentExtents = icomponent.getExtents(0)
+ return self.attributeNamesDict.get(attribName, attribName)
- # [[[TODO: WDW - HACK related to GAIL bug where table column
- # headers seem to be ignored:
- # http://bugzilla.gnome.org/show_bug.cgi?id=325809. The
- # problem is that this causes getAccessibleAtPoint to return
- # the cell effectively below the real cell at a given point,
- # making a mess of everything. So...we just manually add in
- # showing headers for now. The remainder of the logic below
- # accidentally accounts for this offset, yet it should also
- # work when bug 325809 is fixed.]]]
- #
- try:
- table = parent.queryTable()
- except NotImplementedError:
- table = None
-
- if table:
- for i in range(0, table.nColumns):
- header = table.getColumnHeader(i)
- if header:
- extents = header.queryComponent().getExtents(0)
- stateset = header.getState()
- if stateset.contains(pyatspi.STATE_SHOWING) \
- and (extents.x >= 0) and (extents.y >= 0) \
- and (extents.width > 0) and (extents.height > 0) \
- and self.visible(extents.x, extents.y,
- extents.width, extents.height,
- parentExtents.x, parentExtents.y,
- parentExtents.width,
- parentExtents.height):
- descendants.append(header)
-
- # This algorithm goes left to right, top to bottom while attempting
- # to do *some* optimization over queries. It could definitely be
- # improved. The gridSize is a minimal chunk to jump around in the
- # table.
- #
- gridSize = 7
- currentY = parentExtents.y
- while currentY < (parentExtents.y + parentExtents.height):
- currentX = parentExtents.x
- minHeight = sys.maxint
- while currentX < (parentExtents.x + parentExtents.width):
- child = \
- icomponent.getAccessibleAtPoint(currentX, currentY + 1, 0)
- if child:
- extents = child.queryComponent().getExtents(0)
- if extents.x >= 0 and extents.y >= 0:
- newX = extents.x + extents.width
- minHeight = min(minHeight, extents.height)
- if not descendants.count(child):
- descendants.append(child)
- else:
- newX = currentX + gridSize
- else:
- newX = currentX + gridSize
- if newX <= currentX:
- currentX += gridSize
- else:
- currentX = newX
- if minHeight == sys.maxint:
- minHeight = gridSize
- currentY += minHeight
+ def getAppNameForAttribute(self, attribName):
+ """Converts the given Atk attribute name into the application's
+ equivalent. This is necessary because an application or toolkit
+ (e.g. Gecko) might invent entirely new names for the same text
+ attributes.
- return descendants
+ Arguments:
+ - attribName: The name of the text attribute
- def visible(self,
- ax, ay, awidth, aheight,
- bx, by, bwidth, bheight):
- """Returns true if any portion of region 'a' is in region 'b'
+ Returns the application's equivalent name if found or attribName
+ otherwise.
"""
- highestBottom = min(ay + aheight, by + bheight)
- lowestTop = max(ay, by)
-
- leftMostRightEdge = min(ax + awidth, bx + bwidth)
- rightMostLeftEdge = max(ax, bx)
- visible = False
-
- if (lowestTop <= highestBottom) \
- and (rightMostLeftEdge <= leftMostRightEdge):
- visible = True
- elif (aheight == 0):
- if (awidth == 0):
- visible = (lowestTop == highestBottom) \
- and (leftMostRightEdge == rightMostLeftEdge)
- else:
- visible = leftMostRightEdge <= rightMostLeftEdge
- elif (awidth == 0):
- visible = (lowestTop <= highestBottom)
+ for key, value in self.attributeNamesDict.items():
+ if value == attribName:
+ return key
- return visible
+ return attribName
def getFlatReviewContext(self):
"""Returns the flat review context, creating one if necessary."""
@@ -5007,49 +5220,30 @@ class Script(script.Script):
return self.flatReviewContext
- def toggleFlatReviewMode(self, inputEvent=None):
- """Toggles between flat review mode and focus tracking mode."""
+ def drawOutline(self, x, y, width, height):
+ """Draws an outline around the accessible, erasing the last drawn
+ outline in the process."""
- if self.flatReviewContext:
- if inputEvent:
- # Translators: the 'flat review' feature of Orca
- # allows the blind user to explore the text in a
- # window in a 2D fashion. That is, Orca treats all
- # the text from all objects in a window (e.g.,
- # buttons, labels, etc.) as a sequence of words in a
- # sequence of lines. The flat review feature allows
- # the user to explore this text by the {previous,next}
- # {line,word,character}. This message lets the user know
- # they have left the flat review feature.
- #
- if settings.speechVerbosityLevel \
- != settings.VERBOSITY_LEVEL_BRIEF:
- speech.speak(_("Leaving flat review."))
- self.drawOutline(-1, 0, 0, 0)
- self.flatReviewContext = None
- self.updateBraille(orca_state.locusOfFocus)
+ if (x == -1) and (y == 0) and (width == 0) and (height == 0):
+ outline.erase()
else:
- if inputEvent:
- # Translators: the 'flat review' feature of Orca
- # allows the blind user to explore the text in a
- # window in a 2D fashion. That is, Orca treats all
- # the text from all objects in a window (e.g.,
- # buttons, labels, etc.) as a sequence of words in a
- # sequence of lines. The flat review feature allows
- # the user to explore this text by the {previous,next}
- # {line,word,character}. This message lets the user know
- # they have entered the flat review feature.
- #
- if settings.speechVerbosityLevel \
- != settings.VERBOSITY_LEVEL_BRIEF:
- speech.speak(_("Entering flat review."))
- context = self.getFlatReviewContext()
- [wordString, x, y, width, height] = \
- context.getCurrent(flat_review.Context.WORD)
- self.drawOutline(x, y, width, height)
- self._reviewCurrentItem(inputEvent, self.targetCursorCell)
+ outline.draw(x, y, width, height)
- return True
+ def outlineAccessible(self, accessible):
+ """Draws a rectangular outline around the accessible, erasing the
+ last drawn rectangle in the process."""
+
+ try:
+ component = accessible.queryComponent()
+ except AttributeError:
+ self.drawOutline(-1, 0, 0, 0)
+ except NotImplementedError:
+ pass
+ else:
+ visibleRectangle = \
+ component.getExtents(pyatspi.DESKTOP_COORDS)
+ self.drawOutline(visibleRectangle.x, visibleRectangle.y,
+ visibleRectangle.width, visibleRectangle.height)
def updateBrailleReview(self, targetCursorCell=0):
"""Obtains the braille regions for the current flat review line
@@ -5109,740 +5303,6 @@ class Script(script.Script):
0) # character index
break
- def panBrailleLeft(self, inputEvent=None, panAmount=0):
- """Pans the braille display to the left. If panAmount is non-zero,
- the display is panned by that many cells. If it is 0, the display
- is panned one full display width. In flat review mode, panning
- beyond the beginning will take you to the end of the previous line.
-
- In focus tracking mode, the cursor stays at its logical position.
- In flat review mode, the review cursor moves to character
- associated with cell 0."""
-
- if self.flatReviewContext:
- if self.isBrailleBeginningShowing():
- self.flatReviewContext.goBegin(flat_review.Context.LINE)
- self.reviewPreviousCharacter(inputEvent)
- else:
- self.panBrailleInDirection(panAmount, panToLeft=True)
-
- # This will update our target cursor cell
- #
- self._setFlatReviewContextToBeginningOfBrailleDisplay()
-
- [charString, x, y, width, height] = \
- self.flatReviewContext.getCurrent(flat_review.Context.CHAR)
- self.drawOutline(x, y, width, height)
-
- self.targetCursorCell = 1
- self.updateBrailleReview(self.targetCursorCell)
- elif self.isBrailleBeginningShowing() and orca_state.locusOfFocus \
- and self.isTextArea(orca_state.locusOfFocus):
-
- # If we're at the beginning of a line of a multiline text
- # area, then force it's caret to the end of the previous
- # line. The assumption here is that we're currently
- # viewing the line that has the caret -- which is a pretty
- # good assumption for focus tacking mode. When we set the
- # caret position, we will get a caret event, which will
- # then update the braille.
- #
- text = orca_state.locusOfFocus.queryText()
- [lineString, startOffset, endOffset] = text.getTextAtOffset(
- text.caretOffset,
- pyatspi.TEXT_BOUNDARY_LINE_START)
- movedCaret = False
- if startOffset > 0:
- movedCaret = text.setCaretOffset(startOffset - 1)
-
- # If we didn't move the caret and we're in a terminal, we
- # jump into flat review to review the text. See
- # http://bugzilla.gnome.org/show_bug.cgi?id=482294.
- #
- if (not movedCaret) \
- and (orca_state.locusOfFocus.getRole() \
- == pyatspi.ROLE_TERMINAL):
- context = self.getFlatReviewContext()
- context.goBegin(flat_review.Context.LINE)
- self.reviewPreviousCharacter(inputEvent)
- else:
- self.panBrailleInDirection(panAmount, panToLeft=True)
- # We might be panning through a flashed message.
- #
- braille.resetFlashTimer()
- self.refreshBraille(False, stopFlash=False)
-
- return True
-
- def panBrailleLeftOneChar(self, inputEvent=None):
- """Nudges the braille display one character to the left.
-
- In focus tracking mode, the cursor stays at its logical position.
- In flat review mode, the review cursor moves to character
- associated with cell 0."""
-
- self.panBrailleLeft(inputEvent, 1)
-
- def panBrailleRight(self, inputEvent=None, panAmount=0):
- """Pans the braille display to the right. If panAmount is non-zero,
- the display is panned by that many cells. If it is 0, the display
- is panned one full display width. In flat review mode, panning
- beyond the end will take you to the begininng of the next line.
-
- In focus tracking mode, the cursor stays at its logical position.
- In flat review mode, the review cursor moves to character
- associated with cell 0."""
-
- if self.flatReviewContext:
- if self.isBrailleEndShowing():
- self.flatReviewContext.goEnd(flat_review.Context.LINE)
- self.reviewNextCharacter(inputEvent)
- else:
- self.panBrailleInDirection(panAmount, panToLeft=False)
-
- # This will update our target cursor cell
- #
- self._setFlatReviewContextToBeginningOfBrailleDisplay()
-
- [charString, x, y, width, height] = \
- self.flatReviewContext.getCurrent(flat_review.Context.CHAR)
-
- self.drawOutline(x, y, width, height)
-
- self.targetCursorCell = 1
- self.updateBrailleReview(self.targetCursorCell)
- elif self.isBrailleEndShowing() and orca_state.locusOfFocus \
- and self.isTextArea(orca_state.locusOfFocus):
- # If we're at the end of a line of a multiline text area, then
- # force it's caret to the beginning of the next line. The
- # assumption here is that we're currently viewing the line that
- # has the caret -- which is a pretty good assumption for focus
- # tacking mode. When we set the caret position, we will get a
- # caret event, which will then update the braille.
- #
- text = orca_state.locusOfFocus.queryText()
- [lineString, startOffset, endOffset] = text.getTextAtOffset(
- text.caretOffset,
- pyatspi.TEXT_BOUNDARY_LINE_START)
- if endOffset < text.characterCount:
- text.setCaretOffset(endOffset)
- else:
- self.panBrailleInDirection(panAmount, panToLeft=False)
- # We might be panning through a flashed message.
- #
- braille.resetFlashTimer()
- self.refreshBraille(False, stopFlash=False)
-
- return True
-
- def panBrailleRightOneChar(self, inputEvent=None):
- """Nudges the braille display one character to the right.
-
- In focus tracking mode, the cursor stays at its logical position.
- In flat review mode, the review cursor moves to character
- associated with cell 0."""
-
- self.panBrailleRight(inputEvent, 1)
-
- def goBrailleHome(self, inputEvent=None):
- """Returns to the component with focus."""
-
- if self.flatReviewContext:
- return self.toggleFlatReviewMode(inputEvent)
- else:
- return braille.returnToRegionWithFocus(inputEvent)
-
- def setContractedBraille(self, inputEvent=None):
- """Toggles contracted braille."""
-
- self._setContractedBraille(inputEvent)
- return True
-
- def processRoutingKey(self, inputEvent=None):
- """Processes a cursor routing key."""
-
- braille.processRoutingKey(inputEvent)
- return True
-
- def processBrailleCutBegin(self, inputEvent=None):
- """Clears the selection and moves the caret offset in the currently
- active text area.
- """
-
- obj, caretOffset = self.getBrailleCaretContext(inputEvent)
-
- if caretOffset >= 0:
- self.clearTextSelection(obj)
- self.setCaretOffset(obj, caretOffset)
-
- return True
-
- def processBrailleCutLine(self, inputEvent=None):
- """Extends the text selection in the currently active text
- area and also copies the selected text to the system clipboard."""
-
- obj, caretOffset = self.getBrailleCaretContext(inputEvent)
-
- if caretOffset >= 0:
- self.adjustTextSelection(obj, caretOffset)
- texti = obj.queryText()
- startOffset, endOffset = texti.getSelection(0)
- import gtk
- clipboard = gtk.clipboard_get()
- clipboard.set_text(texti.getText(startOffset, endOffset))
-
- return True
-
- def getAbsoluteMouseCoordinates(self):
- """Gets the absolute position of the mouse pointer."""
-
- import gtk
- rootWindow = gtk.Window().get_screen().get_root_window()
- x, y, modifiers = rootWindow.get_pointer()
-
- return x, y
-
- def routePointerToItem(self, inputEvent=None):
- """Moves the mouse pointer to the current item."""
-
- # Store the original location for scripts which want to restore
- # it later.
- #
- self.oldMouseCoordinates = self.getAbsoluteMouseCoordinates()
- self.lastMouseRoutingTime = time.time()
- if self.flatReviewContext:
- self.flatReviewContext.routeToCurrent()
- else:
- try:
- eventsynthesizer.routeToCharacter(orca_state.locusOfFocus)
- except:
- try:
- eventsynthesizer.routeToObject(orca_state.locusOfFocus)
- except:
- # Translators: Orca has a command that allows the user
- # to move the mouse pointer to the current object. If
- # for some reason Orca cannot identify the current
- # location, it will speak this message.
- #
- speech.speak(_("Could not find current location."))
-
- return True
-
- def leftClickReviewItem(self, inputEvent=None):
- """Performs a left mouse button click on the current item."""
-
- if self.flatReviewContext:
- self.flatReviewContext.clickCurrent(1)
- else:
- try:
- eventsynthesizer.clickCharacter(orca_state.locusOfFocus, 1)
- except:
- try:
- eventsynthesizer.clickObject(orca_state.locusOfFocus, 1)
- except:
- # Translators: Orca has a command that allows the user
- # to move the mouse pointer to the current object. If
- # for some reason Orca cannot identify the current
- # location, it will speak this message.
- #
- speech.speak(_("Could not find current location."))
- return True
-
- def rightClickReviewItem(self, inputEvent=None):
- """Performs a right mouse button click on the current item."""
-
- if self.flatReviewContext:
- self.flatReviewContext.clickCurrent(3)
- else:
- try:
- eventsynthesizer.clickCharacter(orca_state.locusOfFocus, 3)
- except:
- try:
- eventsynthesizer.clickObject(orca_state.locusOfFocus, 3)
- except:
- # Translators: Orca has a command that allows the user
- # to move the mouse pointer to the current object. If
- # for some reason Orca cannot identify the current
- # location, it will speak this message.
- #
- speech.speak(_("Could not find current location."))
- return True
-
- def reviewCurrentLine(self, inputEvent):
- """Brailles and speaks the current flat review line."""
-
- self._reviewCurrentLine(inputEvent, 1)
- self.lastReviewCurrentEvent = inputEvent
-
- return True
-
- def reviewSpellCurrentLine(self, inputEvent):
- """Brailles and spells the current flat review line."""
-
- self._reviewCurrentLine(inputEvent, 2)
- self.lastReviewCurrentEvent = inputEvent
-
- return True
-
- def reviewPhoneticCurrentLine(self, inputEvent):
- """Brailles and phonetically spells the current flat review line."""
-
- self._reviewCurrentLine(inputEvent, 3)
- self.lastReviewCurrentEvent = inputEvent
-
- return True
-
- def _reviewCurrentLine(self, inputEvent, speechType=1):
- """Presents the current flat review line via braille and speech.
-
- Arguments:
- - inputEvent - the current input event.
- - speechType - the desired presentation: speak (1), spell (2), or
- phonetic (3)
- """
-
- context = self.getFlatReviewContext()
-
- [lineString, x, y, width, height] = \
- context.getCurrent(flat_review.Context.LINE)
- self.drawOutline(x, y, width, height)
-
- # Don't announce anything from speech if the user used
- # the Braille display as an input device.
- #
- if not isinstance(inputEvent, input_event.BrailleEvent):
- if (not lineString) \
- or (not len(lineString)) \
- or (lineString == "\n"):
- # Translators: "blank" is a short word to mean the
- # user has navigated to an empty line.
- #
- speech.speak(_("blank"))
- elif lineString.isspace():
- # Translators: "white space" is a short phrase to mean the
- # user has navigated to a line with only whitespace on it.
- #
- speech.speak(_("white space"))
- elif lineString.decode("UTF-8").isupper() \
- and (speechType < 2 or speechType > 3):
- speech.speak(lineString, self.voices[settings.UPPERCASE_VOICE])
- elif speechType == 2:
- self.spellCurrentItem(lineString)
- elif speechType == 3:
- self.phoneticSpellCurrentItem(lineString)
- else:
- lineString = self.adjustForRepeats(lineString)
- speech.speak(lineString)
-
- self.updateBrailleReview()
-
- return True
-
- def reviewPreviousLine(self, inputEvent):
- """Moves the flat review context to the beginning of the
- previous line."""
-
- context = self.getFlatReviewContext()
-
- moved = context.goPrevious(flat_review.Context.LINE,
- flat_review.Context.WRAP_LINE)
-
- if moved:
- self._reviewCurrentLine(inputEvent)
- self.targetCursorCell = self.getBrailleCursorCell()
-
- return True
-
- def reviewHome(self, inputEvent):
- """Moves the flat review context to the top left of the current
- window."""
-
- context = self.getFlatReviewContext()
-
- context.goBegin()
-
- self._reviewCurrentLine(inputEvent)
- self.targetCursorCell = self.getBrailleCursorCell()
-
- return True
-
- def reviewNextLine(self, inputEvent):
- """Moves the flat review context to the beginning of the
- next line. Places the flat review cursor at the beginning
- of the line."""
-
- context = self.getFlatReviewContext()
-
- moved = context.goNext(flat_review.Context.LINE,
- flat_review.Context.WRAP_LINE)
-
- if moved:
- self._reviewCurrentLine(inputEvent)
- self.targetCursorCell = self.getBrailleCursorCell()
-
- return True
-
- def reviewBottomLeft(self, inputEvent):
- """Moves the flat review context to the beginning of the
- last line in the window. Places the flat review cursor at
- the beginning of the line."""
-
- context = self.getFlatReviewContext()
-
- context.goEnd(flat_review.Context.WINDOW)
- context.goBegin(flat_review.Context.LINE)
- self._reviewCurrentLine(inputEvent)
- self.targetCursorCell = self.getBrailleCursorCell()
-
- return True
-
- def reviewEnd(self, inputEvent):
- """Moves the flat review context to the end of the
- last line in the window. Places the flat review cursor
- at the end of the line."""
-
- context = self.getFlatReviewContext()
- context.goEnd()
-
- self._reviewCurrentLine(inputEvent)
- self.targetCursorCell = self.getBrailleCursorCell()
-
- return True
-
- def reviewCurrentItem(self, inputEvent, targetCursorCell=0):
- """Brailles and speaks the current item to the user."""
-
- self._reviewCurrentItem(inputEvent, targetCursorCell, 1)
- self.lastReviewCurrentEvent = inputEvent
-
- return True
-
- def reviewSpellCurrentItem(self, inputEvent, targetCursorCell=0):
- """Brailles and spells the current item to the user."""
-
- self._reviewCurrentItem(inputEvent, targetCursorCell, 2)
- self.lastReviewCurrentEvent = inputEvent
-
- return True
-
- def reviewPhoneticCurrentItem(self, inputEvent, targetCursorCell=0):
- """Brailles and phonetically spells the current item to the user."""
-
- self._reviewCurrentItem(inputEvent, targetCursorCell, 3)
- self.lastReviewCurrentEvent = inputEvent
-
- return True
-
- def spellCurrentItem(self, itemString):
- """Spell the current flat review word or line.
-
- Arguments:
- - itemString: the string to spell.
- """
-
- for (charIndex, character) in enumerate(itemString.decode("UTF-8")):
- if character.isupper():
- speech.speak(character.encode("UTF-8"),
- self.voices[settings.UPPERCASE_VOICE])
- else:
- speech.speak(character.encode("UTF-8"))
-
- def _reviewCurrentItem(self, inputEvent, targetCursorCell=0,
- speechType=1):
- """Presents the current item to the user.
-
- Arguments:
- - inputEvent - the current input event.
- - targetCursorCell - if non-zero, the target braille cursor cell.
- - speechType - the desired presentation: speak (1), spell (2), or
- phonetic (3).
- """
-
- context = self.getFlatReviewContext()
- [wordString, x, y, width, height] = \
- context.getCurrent(flat_review.Context.WORD)
- self.drawOutline(x, y, width, height)
-
- # Don't announce anything from speech if the user used
- # the Braille display as an input device.
- #
- if not isinstance(inputEvent, input_event.BrailleEvent):
- if (not wordString) \
- or (not len(wordString)) \
- or (wordString == "\n"):
- # Translators: "blank" is a short word to mean the
- # user has navigated to an empty line.
- #
- speech.speak(_("blank"))
- else:
- [lineString, x, y, width, height] = \
- context.getCurrent(flat_review.Context.LINE)
- if lineString == "\n":
- # Translators: "blank" is a short word to mean the
- # user has navigated to an empty line.
- #
- speech.speak(_("blank"))
- elif wordString.isspace():
- # Translators: "white space" is a short phrase to mean the
- # user has navigated to a line with only whitespace on it.
- #
- speech.speak(_("white space"))
- elif wordString.decode("UTF-8").isupper() and speechType == 1:
- speech.speak(wordString,
- self.voices[settings.UPPERCASE_VOICE])
- elif speechType == 2:
- self.spellCurrentItem(wordString)
- elif speechType == 3:
- self.phoneticSpellCurrentItem(wordString)
- elif speechType == 1:
- wordString = self.adjustForRepeats(wordString)
- speech.speak(wordString)
-
- self.updateBrailleReview(targetCursorCell)
-
- return True
-
- def reviewCurrentAccessible(self, inputEvent):
- context = self.getFlatReviewContext()
- [zoneString, x, y, width, height] = \
- context.getCurrent(flat_review.Context.ZONE)
- self.drawOutline(x, y, width, height)
-
- # Don't announce anything from speech if the user used
- # the Braille display as an input device.
- #
- if not isinstance(inputEvent, input_event.BrailleEvent):
- utterances = self.speechGenerator.generateSpeech(
- context.getCurrentAccessible())
- utterances.extend(self.tutorialGenerator.getTutorial(
- context.getCurrentAccessible(), False))
- speech.speak(utterances)
- return True
-
- def reviewPreviousItem(self, inputEvent):
- """Moves the flat review context to the previous item. Places
- the flat review cursor at the beginning of the item."""
-
- context = self.getFlatReviewContext()
-
- moved = context.goPrevious(flat_review.Context.WORD,
- flat_review.Context.WRAP_LINE)
-
- if moved:
- self._reviewCurrentItem(inputEvent)
- self.targetCursorCell = self.getBrailleCursorCell()
-
- return True
-
- def reviewNextItem(self, inputEvent):
- """Moves the flat review context to the next item. Places
- the flat review cursor at the beginning of the item."""
-
- context = self.getFlatReviewContext()
-
- moved = context.goNext(flat_review.Context.WORD,
- flat_review.Context.WRAP_LINE)
-
- if moved:
- self._reviewCurrentItem(inputEvent)
- self.targetCursorCell = self.getBrailleCursorCell()
-
- return True
-
- def reviewCurrentCharacter(self, inputEvent):
- """Brailles and speaks the current flat review character."""
-
- self._reviewCurrentCharacter(inputEvent, 1)
- self.lastReviewCurrentEvent = inputEvent
-
- return True
-
- def reviewSpellCurrentCharacter(self, inputEvent):
- """Brailles and 'spells' (phonetically) the current flat review
- character.
- """
-
- self._reviewCurrentCharacter(inputEvent, 2)
- self.lastReviewCurrentEvent = inputEvent
-
- return True
-
- def reviewUnicodeCurrentCharacter(self, inputEvent):
- """Brailles and speaks unicode information about the current flat
- review character.
- """
-
- self._reviewCurrentCharacter(inputEvent, 3)
- self.lastReviewCurrentEvent = inputEvent
-
- return True
-
- def _reviewCurrentCharacter(self, inputEvent, speechType=1):
- """Presents the current flat review character via braille and speech.
-
- Arguments:
- - inputEvent - the current input event.
- - speechType - the desired presentation:
- speak (1),
- phonetic (2)
- unicode value information (3)
- """
-
- context = self.getFlatReviewContext()
-
- [charString, x, y, width, height] = \
- context.getCurrent(flat_review.Context.CHAR)
- self.drawOutline(x, y, width, height)
-
- # Don't announce anything from speech if the user used
- # the Braille display as an input device.
- #
- if not isinstance(inputEvent, input_event.BrailleEvent):
- if (not charString) or (not len(charString)):
- # Translators: "blank" is a short word to mean the
- # user has navigated to an empty line.
- #
- speech.speak(_("blank"))
- else:
- [lineString, x, y, width, height] = \
- context.getCurrent(flat_review.Context.LINE)
- if lineString == "\n" and speechType != 3:
- # Translators: "blank" is a short word to mean the
- # user has navigated to an empty line.
- #
- speech.speak(_("blank"))
- elif speechType == 3:
- self.speakUnicodeCharacter(charString)
- elif speechType == 2:
- self.phoneticSpellCurrentItem(charString)
- elif charString.decode("UTF-8").isupper():
- speech.speakCharacter(charString,
- self.voices[settings.UPPERCASE_VOICE])
- else:
- speech.speakCharacter(charString)
-
- self.updateBrailleReview()
-
- return True
-
- def speakUnicodeCharacter(self, character):
- """ Speaks some information about an unicode character.
- At the Momment it just anounces the character unicode number but
- this information may be changed in the future
-
- Arguments:
- - character: the character to speak information of
- """
- # Translators: this is information about a unicode character
- # reported to the user. The value is the unicode number value
- # of this character in hex.
- #
- speech.speak(_("Unicode %s") % self.getUnicodeValueString(character))
-
- def getUnicodeValueString(self, character):
- """ Returns a four hex digit representaiton of the given character
-
- Arguments:
- - The character to return representation
-
- Returns a string representaition of the given character unicode vlue
- """
- try:
- if not isinstance(character, unicode):
- character = character.decode('UTF-8')
- return "%04x" % ord(character)
- except:
- debug.printException(debug.LEVEL_WARNING)
- return ""
-
- def reviewPreviousCharacter(self, inputEvent):
- """Moves the flat review context to the previous character. Places
- the flat review cursor at character."""
-
- context = self.getFlatReviewContext()
-
- moved = context.goPrevious(flat_review.Context.CHAR,
- flat_review.Context.WRAP_LINE)
-
- if moved:
- self._reviewCurrentCharacter(inputEvent)
- self.targetCursorCell = self.getBrailleCursorCell()
-
- return True
-
- def reviewEndOfLine(self, inputEvent):
- """Moves the flat review context to the end of the line. Places
- the flat review cursor at the end of the line."""
-
- context = self.getFlatReviewContext()
- context.goEnd(flat_review.Context.LINE)
-
- self.reviewCurrentCharacter(inputEvent)
- self.targetCursorCell = self.getBrailleCursorCell()
-
- return True
-
- def reviewNextCharacter(self, inputEvent):
- """Moves the flat review context to the next character. Places
- the flat review cursor at character."""
-
- context = self.getFlatReviewContext()
-
- moved = context.goNext(flat_review.Context.CHAR,
- flat_review.Context.WRAP_LINE)
-
- if moved:
- self._reviewCurrentCharacter(inputEvent)
- self.targetCursorCell = self.getBrailleCursorCell()
-
- return True
-
- def reviewAbove(self, inputEvent):
- """Moves the flat review context to the character most directly
- above the current flat review cursor. Places the flat review
- cursor at character."""
-
- context = self.getFlatReviewContext()
-
- moved = context.goAbove(flat_review.Context.CHAR,
- flat_review.Context.WRAP_LINE)
-
- if moved:
- self._reviewCurrentItem(inputEvent, self.targetCursorCell)
-
- return True
-
- def reviewBelow(self, inputEvent):
- """Moves the flat review context to the character most directly
- below the current flat review cursor. Places the flat review
- cursor at character."""
-
- context = self.getFlatReviewContext()
-
- moved = context.goBelow(flat_review.Context.CHAR,
- flat_review.Context.WRAP_LINE)
-
- if moved:
- self._reviewCurrentItem(inputEvent, self.targetCursorCell)
-
- return True
-
- def showZones(self, inputEvent):
- """Debug routine to paint rectangles around the discrete
- interesting (e.g., text) zones in the active window for
- this application.
- """
-
- flatReviewContext = self.getFlatReviewContext()
- lines = flatReviewContext.lines
- for line in lines:
- lineString = ""
- for zone in line.zones:
- lineString += " '%s' [%s]" % \
- (zone.string, zone.accessible.getRoleName())
- debug.println(debug.LEVEL_OFF, lineString)
- self.flatReviewContext = None
-
def find(self, query=None):
"""Searches for the specified query. If no query is specified,
it searches for the query specified in the Orca Find dialog.
@@ -5872,302 +5332,6 @@ class Script(script.Script):
self.reviewCurrentItem(None)
self.targetCursorCell = self.getBrailleCursorCell()
- def findNext(self, inputEvent):
- """Searches forward for the next instance of the string
- searched for via the Orca Find dialog. Other than direction
- and the starting point, the search options initially specified
- (case sensitivity, window wrap, and full/partial match) are
- preserved.
- """
-
- lastQuery = find.getLastQuery()
- if lastQuery:
- lastQuery.searchBackwards = False
- lastQuery.startAtTop = False
- self.find(lastQuery)
- else:
- orca.showFindGUI()
-
- def findPrevious(self, inputEvent):
- """Searches backwards for the next instance of the string
- searched for via the Orca Find dialog. Other than direction
- and the starting point, the search options initially specified
- (case sensitivity, window wrap, and full/or partial match) are
- preserved.
- """
-
- lastQuery = find.getLastQuery()
- if lastQuery:
- lastQuery.searchBackwards = True
- lastQuery.startAtTop = False
- self.find(lastQuery)
- else:
- orca.showFindGUI()
-
- def goToBookmark(self, inputEvent):
- """ Go to the bookmark indexed by inputEvent.hw_code. Delegates to
- Bookmark.goToBookmark """
- bookmarks = self.getBookmarks()
- bookmarks.goToBookmark(inputEvent)
-
- def addBookmark(self, inputEvent):
- """ Add an in-page accessible object bookmark for this key.
- Delegates to Bookmark.addBookmark """
- bookmarks = self.getBookmarks()
- bookmarks.addBookmark(inputEvent)
-
- def bookmarkCurrentWhereAmI(self, inputEvent):
- """ Report "Where am I" information for this bookmark relative to the
- current pointer location. Delegates to
- Bookmark.bookmarkCurrentWhereAmI"""
- bookmarks = self.getBookmarks()
- bookmarks.bookmarkCurrentWhereAmI(inputEvent)
-
- def saveBookmarks(self, inputEvent):
- """ Save the bookmarks for this script. Delegates to
- Bookmark.saveBookmarks """
- bookmarks = self.getBookmarks()
- bookmarks.saveBookmarks(inputEvent)
-
- def goToNextBookmark(self, inputEvent):
- """ Go to the next bookmark location. If no bookmark has yet to be
- selected, the first bookmark will be used. Delegates to
- Bookmark.goToNextBookmark """
- bookmarks = self.getBookmarks()
- bookmarks.goToNextBookmark(inputEvent)
-
- def goToPrevBookmark(self, inputEvent):
- """ Go to the previous bookmark location. If no bookmark has yet to
- be selected, the first bookmark will be used. Delegates to
- Bookmark.goToPrevBookmark """
- bookmarks = self.getBookmarks()
- bookmarks.goToPrevBookmark(inputEvent)
-
-########################################################################
-# #
-# DEBUG support. #
-# #
-########################################################################
-
- def _isInterestingObj(self, obj):
- import inspect
-
- interesting = False
-
- if getattr(obj, "__class__", None):
- name = obj.__class__.__name__
- if name not in ["function",
- "type",
- "list",
- "dict",
- "tuple",
- "wrapper_descriptor",
- "module",
- "method_descriptor",
- "member_descriptor",
- "instancemethod",
- "builtin_function_or_method",
- "frame",
- "classmethod",
- "classmethod_descriptor",
- "_Environ",
- "MemoryError",
- "_Printer",
- "_Helper",
- "getset_descriptor",
- "weakref",
- "property",
- "cell",
- "staticmethod",
- "EventListener",
- "KeystrokeListener",
- "KeyBinding",
- "InputEventHandler",
- "Rolename"]:
- try:
- filename = inspect.getabsfile(obj.__class__)
- if filename.index("orca"):
- interesting = True
- except:
- pass
-
- return interesting
-
- def _detectCycle(self, obj, visitedObjs, indent=""):
- """Attempts to discover a cycle in object references."""
-
- # [[[TODO: WDW - not sure this really works.]]]
-
- import gc
- visitedObjs.append(obj)
- for referent in gc.get_referents(obj):
- try:
- if visitedObjs.index(referent):
- if self._isInterestingObj(referent):
- print indent, "CYCLE!!!!", `referent`
- break
- except:
- pass
- self._detectCycle(referent, visitedObjs, " " + indent)
- visitedObjs.remove(obj)
-
- def printMemoryUsageHandler(self, inputEvent):
- """Prints memory usage information."""
- print 'TODO: print something useful for memory debugging'
-
- def printAppsHandler(self, inputEvent=None):
- """Prints a list of all applications to stdout."""
- self.printApps()
- return True
-
- def printAncestryHandler(self, inputEvent=None):
- """Prints the ancestry for the current locusOfFocus"""
- self.printAncestry(orca_state.locusOfFocus)
- return True
-
- def printHierarchyHandler(self, inputEvent=None):
- """Prints the application for the current locusOfFocus"""
- if orca_state.locusOfFocus:
- self.printHierarchy(orca_state.locusOfFocus.getApplication(),
- orca_state.locusOfFocus)
- return True
-
-# Routines that were previously in util.py, but that have now been moved
-# here so that they can be customized in application scripts if so desired.
-#
-
- def isSameObject(self, obj1, obj2):
- if (obj1 == obj2):
- return True
- elif (not obj1) or (not obj2):
- return False
-
- try:
- if (obj1.name != obj2.name) or (obj1.getRole() != obj2.getRole()):
- return False
- else:
- # Gecko sometimes creates multiple accessibles to represent
- # the same object. If the two objects have the same name
- # and the same role, check the extents. If those also match
- # then the two objects are for all intents and purposes the
- # same object.
- #
- extents1 = \
- obj1.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
- extents2 = \
- obj2.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
- if (extents1.x == extents2.x) and \
- (extents1.y == extents2.y) and \
- (extents1.width == extents2.width) and \
- (extents1.height == extents2.height):
- return True
-
- # When we're looking at children of objects that manage
- # their descendants, we will often get different objects
- # that point to the same logical child. We want to be able
- # to determine if two objects are in fact pointing to the
- # same child.
- # If we cannot do so easily (i.e., object equivalence), we examine
- # the hierarchy and the object index at each level.
- #
- parent1 = obj1
- parent2 = obj2
- while (parent1 and parent2 and \
- parent1.getState().contains( \
- pyatspi.STATE_TRANSIENT) and \
- parent2.getState().contains(pyatspi.STATE_TRANSIENT)):
- if parent1.getIndexInParent() != parent2.getIndexInParent():
- return False
- parent1 = parent1.parent
- parent2 = parent2.parent
- if parent1 and parent2 and parent1 == parent2:
- return self.getRealActiveDescendant(obj1).name == \
- self.getRealActiveDescendant(obj2).name
- except:
- pass
-
- # In java applications, TRANSIENT state is missing for tree items
- # (fix for bug #352250)
- #
- try:
- parent1 = obj1
- parent2 = obj2
- while parent1 and parent2 and \
- parent1.getRole() == pyatspi.ROLE_LABEL and \
- parent2.getRole() == pyatspi.ROLE_LABEL:
- if parent1.getIndexInParent() != parent2.getIndexInParent():
- return False
- parent1 = parent1.parent
- parent2 = parent2.parent
- if parent1 and parent2 and parent1 == parent2:
- return True
- except:
- pass
-
- return False
-
- def appendString(self, text, newText, delimiter=" "):
- """Appends the newText to the given text with the delimiter in between
- and returns the new string. Edge cases, such as no initial text or
- no newText, are handled gracefully."""
-
- if (not newText) or (len(newText) == 0):
- return text
- elif text and len(text):
- return text + delimiter + newText
- else:
- return newText
-
- def __hasLabelForRelation(self, label):
- """Check if label has a LABEL_FOR relation
-
- Arguments:
- - label: the label in question
-
- Returns TRUE if label has a LABEL_FOR relation.
- """
- if (not label) or (label.getRole() != pyatspi.ROLE_LABEL):
- return False
-
- relations = label.getRelationSet()
-
- for relation in relations:
- if relation.getRelationType() \
- == pyatspi.RELATION_LABEL_FOR:
- return True
-
- return False
-
- def __isLabeling(self, label, obj):
- """Check if label is connected via LABEL_FOR relation with object
-
- Arguments:
- - obj: the object in question
- - labeled: the label in question
-
- Returns TRUE if label has a relation LABEL_FOR for object.
- """
-
- if (not obj) \
- or (not label) \
- or (label.getRole() != pyatspi.ROLE_LABEL):
- return False
-
- relations = label.getRelationSet()
- if not relations:
- return False
-
- for relation in relations:
- if relation.getRelationType() \
- == pyatspi.RELATION_LABEL_FOR:
-
- for i in range(0, relation.getNTargets()):
- target = relation.getTarget(i)
- if target == obj:
- return True
-
- return False
-
def getUnicodeCurrencySymbols(self):
"""Return a list of the unicode currency symbols, populating the list
if this is the first time that this routine has been called.
@@ -6205,465 +5369,9 @@ class Script(script.Script):
return self._unicodeCurrencySymbols
- def findDisplayedLabel(self, obj):
- """Return a list of the objects that are labelling this object.
-
- Argument:
- - obj: the object in question
-
- Returns a list of the objects that are labelling this object.
- """
-
- # For some reason, some objects are labelled by the same thing
- # more than once. Go figure, but we need to check for this.
- #
- label = []
- relations = obj.getRelationSet()
- allTargets = []
-
- for relation in relations:
- if relation.getRelationType() \
- == pyatspi.RELATION_LABELLED_BY:
-
- # The object can be labelled by more than one thing, so we just
- # get all the labels (from unique objects) and append them
- # together. An example of such objects live in the "Basic"
- # page of the gnome-accessibility-keyboard-properties app.
- # The "Delay" and "Speed" objects are labelled both by
- # their names and units.
- #
- for i in range(0, relation.getNTargets()):
- target = relation.getTarget(i)
- if not target in allTargets:
- allTargets.append(target)
- label.append(target)
-
- # [[[TODO: HACK - we've discovered oddness in hierarchies such as
- # the gedit Edit->Preferences dialog. In this dialog, we have
- # labeled groupings of objects. The grouping is done via a FILLER
- # with two children - one child is the overall label, and the
- # other is the container for the grouped objects. When we detect
- # this, we add the label to the overall context.
- #
- # We are also looking for objects which have a PANEL or a FILLER as
- # parent, and its parent has more children. Through these children,
- # a potential label with LABEL_FOR relation may exists. We want to
- # present this label.
- # This case can be seen in FileChooserDemo application, in Open dialog
- # window, the line with "Look In" label, a combobox and some
- # presentation buttons.
- #
- # Finally, we are searching the hierarchy of embedded components for
- # children that are labels.]]]
- #
- if not len(label):
- potentialLabels = []
- useLabel = False
- if (obj.getRole() == pyatspi.ROLE_EMBEDDED):
- candidate = obj
- while candidate.childCount:
- candidate = candidate[0]
- # The parent of this object may contain labels
- # or it may contain filler that contains labels.
- #
- candidate = candidate.parent
- for child in candidate:
- if child.getRole() == pyatspi.ROLE_FILLER:
- candidate = child
- break
- # If there are labels in this embedded component,
- # they should be here.
- #
- for child in candidate:
- if child.getRole() == pyatspi.ROLE_LABEL:
- useLabel = True
- potentialLabels.append(child)
- elif ((obj.getRole() == pyatspi.ROLE_FILLER) \
- or (obj.getRole() == pyatspi.ROLE_PANEL)) \
- and (obj.childCount == 2):
- child0, child1 = obj
- child0_role = child0.getRole()
- child1_role = child1.getRole()
- if child0_role == pyatspi.ROLE_LABEL \
- and not self.__hasLabelForRelation(child0) \
- and child1_role in [pyatspi.ROLE_FILLER, \
- pyatspi.ROLE_PANEL]:
- useLabel = True
- potentialLabels.append(child0)
- elif child1_role == pyatspi.ROLE_LABEL \
- and not self.__hasLabelForRelation(child1) \
- and child0_role in [pyatspi.ROLE_FILLER, \
- pyatspi.ROLE_PANEL]:
- useLabel = True
- potentialLabels.append(child1)
- else:
- parent = obj.parent
- if parent and \
- ((parent.getRole() == pyatspi.ROLE_FILLER) \
- or (parent.getRole() == pyatspi.ROLE_PANEL)):
- for potentialLabel in parent:
- try:
- useLabel = self.__isLabeling(potentialLabel, obj)
- if useLabel:
- potentialLabels.append(potentialLabel)
- break
- except:
- pass
-
- if useLabel and len(potentialLabels):
- label = potentialLabels
-
- return label
-
- def getDisplayedLabel(self, obj):
- """If there is an object labelling the given object, return the
- text being displayed for the object labelling this object.
- Otherwise, return None.
-
- Argument:
- - obj: the object in question
-
- Returns the string of the object labelling this object, or None
- if there is nothing of interest here.
- """
-
- try:
- return self.generatorCache[self.DISPLAYED_LABEL][obj]
- except:
- if not self.generatorCache.has_key(self.DISPLAYED_LABEL):
- self.generatorCache[self.DISPLAYED_LABEL] = {}
- labelString = None
-
- labels = self.findDisplayedLabel(obj)
- for label in labels:
- labelString = self.appendString(labelString,
- self.getDisplayedText(label))
-
- self.generatorCache[self.DISPLAYED_LABEL][obj] = labelString
- return self.generatorCache[self.DISPLAYED_LABEL][obj]
-
- def __getDisplayedTextInComboBox(self, combo):
-
- """Returns the text being displayed in a combo box. If nothing is
- displayed, then None is returned.
-
- Arguments:
- - combo: the combo box
-
- Returns the text in the combo box or an empty string if nothing is
- displayed.
- """
-
- displayedText = None
-
- # Find the text displayed in the combo box. This is either:
- #
- # 1) The last text object that's a child of the combo box
- # 2) The selected child of the combo box.
- # 3) The contents of the text of the combo box itself when
- # treated as a text object.
- #
- # Preference is given to #1, if it exists.
- #
- # If the label of the combo box is the same as the utterance for
- # the child object, then this utterance is only displayed once.
- #
- # [[[TODO: WDW - Combo boxes are complex beasts. This algorithm
- # needs serious work. Logged as bugzilla bug 319745.]]]
- #
- textObj = None
- for child in combo:
- if child and child.getRole() == pyatspi.ROLE_TEXT:
- textObj = child
-
- if textObj:
- [displayedText, caretOffset, startOffset] = \
- self.getTextLineAtCaret(textObj)
- #print "TEXTOBJ", displayedText
- else:
- try:
- comboSelection = combo.querySelection()
- selectedItem = comboSelection.getSelectedChild(0)
- except:
- selectedItem = None
-
- if selectedItem:
- displayedText = self.getDisplayedText(selectedItem)
- #print "SELECTEDITEM", displayedText
- elif combo.name and len(combo.name):
- # We give preference to the name over the text because
- # the text for combo boxes seems to never change in
- # some cases. The main one where we see this is in
- # the gaim "Join Chat" window.
- #
- displayedText = combo.name
- #print "NAME", displayedText
- else:
- [displayedText, caretOffset, startOffset] = \
- self.getTextLineAtCaret(combo)
- # Set to None instead of empty string.
- displayedText = displayedText or None
- #print "TEXT", displayedText
-
- return displayedText
-
- def getDisplayedText(self, obj):
- """Returns the text being displayed for an object.
-
- Arguments:
- - obj: the object
-
- Returns the text being displayed for an object or None if there isn't
- any text being shown.
- """
-
- try:
- return self.generatorCache[self.DISPLAYED_TEXT][obj]
- except:
- if not self.generatorCache.has_key(self.DISPLAYED_TEXT):
- self.generatorCache[self.DISPLAYED_TEXT] = {}
- displayedText = None
-
- role = obj.getRole()
- if role == pyatspi.ROLE_COMBO_BOX:
- displayedText = self.__getDisplayedTextInComboBox(obj)
- self.generatorCache[self.DISPLAYED_TEXT][obj] = displayedText
- return self.generatorCache[self.DISPLAYED_TEXT][obj]
-
- # The accessible text of an object is used to represent what is
- # drawn on the screen.
- #
- try:
- text = obj.queryText()
- except NotImplementedError:
- pass
- else:
- displayedText = text.getText(0, self.getTextEndOffset(text))
-
- # [[[WDW - HACK to account for things such as Gecko that want
- # to use the EMBEDDED_OBJECT_CHARACTER on a label to hold the
- # object that has the real accessible text for the label. We
- # detect this by the specfic case where the text for the
- # current object is a single EMBEDDED_OBJECT_CHARACTER. In
- # this case, we look to the child for the real text.]]]
- #
- unicodeText = displayedText.decode("UTF-8")
- if unicodeText \
- and (len(unicodeText) == 1) \
- and (unicodeText[0] == self.EMBEDDED_OBJECT_CHARACTER) \
- and obj.childCount > 0:
- try:
- displayedText = self.getDisplayedText(obj[0])
- except:
- debug.printException(debug.LEVEL_WARNING)
- elif unicodeText:
- # [[[TODO: HACK - Welll.....we'll just plain ignore any
- # text with EMBEDDED_OBJECT_CHARACTERs here. We still need a
- # general case to handle this stuff and expand objects
- # with EMBEDDED_OBJECT_CHARACTERs.]]]
- #
- for i in range(0, len(unicodeText)):
- if unicodeText[i] == self.EMBEDDED_OBJECT_CHARACTER:
- displayedText = None
- break
-
- if not displayedText:
- displayedText = obj.name
-
- # [[[WDW - HACK because push buttons can have labels as their
- # children. An example of this is the Font: button on the General
- # tab in the Editing Profile dialog in gnome-terminal.
- #
- if not displayedText and role == pyatspi.ROLE_PUSH_BUTTON:
- for child in obj:
- if child.getRole() == pyatspi.ROLE_LABEL:
- childText = self.getDisplayedText(child)
- if childText and len(childText):
- displayedText = self.appendString(displayedText,
- childText)
-
- self.generatorCache[self.DISPLAYED_TEXT][obj] = displayedText
- return self.generatorCache[self.DISPLAYED_TEXT][obj]
-
- def getTextForValue(self, obj):
- """Returns the text to be displayed for the object's current value.
-
- Arguments:
- - obj: the Accessible object that may or may not have a value.
-
- Returns a string representing the value.
- """
-
- # Use ARIA "valuetext" attribute if present. See
- # http://bugzilla.gnome.org/show_bug.cgi?id=552965
- #
- attributes = obj.getAttributes()
- for attribute in attributes:
- if attribute.startswith("valuetext"):
- return attribute[10:]
-
- try:
- value = obj.queryValue()
- except NotImplementedError:
- return ""
-
- # OK, this craziness is all about trying to figure out the most
- # meaningful formatting string for the floating point values.
- # The number of places to the right of the decimal point should
- # be set by the minimumIncrement, but the minimumIncrement isn't
- # always set. So...we'll default the minimumIncrement to 1/100
- # of the range. But, if max == min, then we'll just go for showing
- # them off to two meaningful digits.
- #
- try:
- minimumIncrement = value.minimumIncrement
- except:
- minimumIncrement = 0.0
-
- if minimumIncrement == 0.0:
- minimumIncrement = (value.maximumValue - value.minimumValue) \
- / 100.0
-
- try:
- decimalPlaces = max(0, -math.log10(minimumIncrement))
- except:
- try:
- decimalPlaces = max(0, -math.log10(value.minimumValue))
- except:
- try:
- decimalPlaces = max(0, -math.log10(value.maximumValue))
- except:
- decimalPlaces = 0
-
- formatter = "%%.%df" % decimalPlaces
- valueString = formatter % value.currentValue
- #minString = formatter % value.minimumValue
- #maxString = formatter % value.maximumValue
-
- # [[[TODO: WDW - probably want to do this as a percentage at some
- # point? Logged as bugzilla bug 319743.]]]
- #
- return valueString
-
- def findFocusedObject(self, root):
- """Returns the accessible that has focus under or including the
- given root.
-
- TODO: This will currently traverse all children, whether they are
- visible or not and/or whether they are children of parents that
- manage their descendants. At some point, this method should be
- optimized to take such things into account.
-
- Arguments:
- - root: the root object where to start searching
-
- Returns the object with the FOCUSED state or None if no object with
- the FOCUSED state can be found.
- """
-
- if root.getState().contains(pyatspi.STATE_FOCUSED):
- return root
-
- for child in root:
- try:
- candidate = self.findFocusedObject(child)
- if candidate:
- return candidate
- except:
- pass
-
- return None
-
- def getRealActiveDescendant(self, obj):
- """Given an object that should be a child of an object that
- manages its descendants, return the child that is the real
- active descendant carrying useful information.
-
- Arguments:
- - obj: an object that should be a child of an object that
- manages its descendants.
- """
-
- try:
- return self.generatorCache[self.REAL_ACTIVE_DESCENDANT][obj]
- except:
- if not self.generatorCache.has_key(self.REAL_ACTIVE_DESCENDANT):
- self.generatorCache[self.REAL_ACTIVE_DESCENDANT] = {}
- realActiveDescendant = None
-
- # If obj is a table cell and all of it's children are table cells
- # (probably cell renderers), then return the first child which has
- # a non zero length text string. If no such object is found, just
- # fall through and use the default approach below. See bug #376791
- # for more details.
- #
- if obj.getRole() == pyatspi.ROLE_TABLE_CELL and obj.childCount:
- nonTableCellFound = False
- for child in obj:
- if child.getRole() != pyatspi.ROLE_TABLE_CELL:
- nonTableCellFound = True
- if not nonTableCellFound:
- for child in obj:
- try:
- text = child.queryText()
- except NotImplementedError:
- continue
- else:
- if text.getText(0, self.getTextEndOffset(text)):
- realActiveDescendant = child
-
- # [[[TODO: WDW - this is an odd hacky thing I've somewhat drawn
- # from Gnopernicus. The notion here is that we get an active
- # descendant changed event, but that object tends to have children
- # itself and we need to decide what to do. Well...the idea here
- # is that the last child (Gnopernicus chooses child(1)), tends to
- # be the child with information. The previous children tend to
- # be non-text or just there for spacing or something. You will
- # see this in the various table demos of gtk-demo and you will
- # also see this in the Contact Source Selector in Evolution.
- #
- # Just note that this is most likely not a really good solution
- # for the general case. That needs more thought. But, this
- # comment is here to remind us this is being done in poor taste
- # and we need to eventually clean up our act.]]]
- #
- if not realActiveDescendant and obj and obj.childCount:
- realActiveDescendant = obj[-1]
-
- self.generatorCache[self.REAL_ACTIVE_DESCENDANT][obj] = \
- realActiveDescendant or obj
- return self.generatorCache[self.REAL_ACTIVE_DESCENDANT][obj]
-
- def isDesiredFocusedItem(self, obj, rolesList):
- """Called to determine if the given object and it's hierarchy of
- parent objects, each have the desired roles.
-
- Arguments:
- - obj: the accessible object to check.
- - rolesList: the list of desired roles for the components and the
- hierarchy of its parents.
-
- Returns True if all roles match.
- """
- current = obj
- for role in rolesList:
- if current is None:
- return False
-
- if not isinstance(role, list):
- role = [role]
-
- if isinstance(role[0], str):
- current_role = current.getRoleName()
- else:
- current_role = current.getRole()
-
- if not current_role in role:
- return False
- current = current.parent
-
- return True
-
+# Routines that were previously in util.py, but that have now been moved
+# here so that they can be customized in application scripts if so desired.
+#
def speakMisspeltWord(self, allTokens, badWord):
"""Called by various spell checking routine to speak the misspelt word,
plus the context that it is being used in.
@@ -6777,7 +5485,7 @@ class Script(script.Script):
lastEndOffset = endOffset
offset = endOffset
- lineString = self.adjustForRepeats(lineString)
+ lineString = self.utilities.adjustForRepeats(lineString)
if lineString.decode("UTF-8").isupper():
voice = settings.voices[settings.UPPERCASE_VOICE]
else:
@@ -6806,424 +5514,6 @@ class Script(script.Script):
if not moreLines:
done = True
- def _addRepeatSegment(self, segment, line, respectPunctuation=True):
- """Add in the latest line segment, adjusting for repeat characters
- and punctuation.
-
- Arguments:
- - segment: the segment of repeated characters.
- - line: the current built-up line to characters to speak.
- - respectPunctuation: if False, ignore punctuation level.
-
- Returns: the current built-up line plus the new segment, after
- adjusting for repeat character counts and punctuation.
- """
-
- style = settings.verbalizePunctuationStyle
- isPunctChar = True
- try:
- level, action = punctuation_settings.getPunctuationInfo(segment[0])
- except:
- isPunctChar = False
- count = len(segment)
- if (count >= settings.repeatCharacterLimit) \
- and (not segment[0] in self.whitespace):
- if (not respectPunctuation) \
- or (isPunctChar and (style <= level)):
- repeatChar = chnames.getCharacterName(segment[0])
- # Translators: Orca will tell you how many characters
- # are repeated on a line of text. For example: "22
- # space characters". The %d is the number and the %s
- # is the spoken word for the character.
- #
- line += " " \
- + ngettext("%(count)d %(repeatChar)s character",
- "%(count)d %(repeatChar)s characters",
- count) \
- % {"count" : count, "repeatChar": repeatChar}
- else:
- line += segment
- else:
- line += segment
-
- return line
-
- def adjustForLinks(self, obj, line, startOffset):
- """Adjust line to include the word "link" after any hypertext links.
-
- 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.
- """
-
- line = line.decode("UTF-8")
- endOffset = startOffset + len(line)
-
- try:
- hyperText = obj.queryHypertext()
- nLinks = hyperText.getNLinks()
- except:
- nLinks = 0
-
- adjustedLine = list(line)
- for n in range(nLinks, 0, -1):
- link = hyperText.getLink(n - 1)
-
- # We only care about links in the string, line:
- #
- if startOffset < link.endIndex < endOffset:
- index = link.endIndex - startOffset
- elif startOffset <= link.startIndex < endOffset:
- index = len(line) - 1
- else:
- continue
-
- # Translators: this indicates that this piece of
- # text is a hypertext link.
- #
- linkString = " " + _("link")
-
- # If the link was not followed by a whitespace or punctuation
- # character, then add in a space to make it more presentable.
- #
- nextChar = adjustedLine[index]
- if not (nextChar in self.whitespace \
- or punctuation_settings.getPunctuationInfo(nextChar)):
- linkString += " "
- adjustedLine[index:index] = linkString
-
- return "".join(adjustedLine).encode("UTF-8")
-
- def adjustForRepeats(self, line):
- """Adjust line to include repeat character counts.
- As some people will want this and others might not,
- there is a setting in settings.py that determines
- whether this functionality is enabled.
-
- repeatCharacterLimit = <n>
-
- If <n> is 0, then there would be no repeat characters.
- Otherwise <n> would be the number of same characters (or more)
- in a row that cause the repeat character count output.
- If the value is set to 1, 2 or 3 then it's treated as if it was
- zero. In other words, no repeat character count is given.
-
- Arguments:
- - line: the string to adjust for repeat character counts.
-
- Returns: a new line adjusted for repeat character counts (if enabled).
- """
-
- line = line.decode("UTF-8")
-
- if (len(line) < 4) or (settings.repeatCharacterLimit < 4):
- return line.encode("UTF-8")
-
- newLine = u''
- segment = lastChar = line[0]
-
- multipleChars = False
- for i in range(1, len(line)):
- if line[i] == lastChar:
- segment += line[i]
- else:
- multipleChars = True
- newLine = self._addRepeatSegment(segment, newLine)
- segment = line[i]
-
- lastChar = line[i]
-
- newLine = self._addRepeatSegment(segment, newLine, multipleChars)
-
- # Pylint is confused and flags this with the following error:
- #
- # E1103:5188:Script.adjustForRepeats: Instance of 'True' has
- # no 'encode' member (but some types could not be inferred)
- #
- # We know newLine is a unicode string, so we'll just tell pylint
- # that we know what we are doing.
- #
- # pylint: disable-msg=E1103
-
- return newLine.encode("UTF-8")
-
- def getCharacterOffsetInParent(self, obj):
- """Returns the character offset of the embedded object
- character for this object in its parent's accessible text.
-
- Arguments:
- - obj: an Accessible that should implement the accessible hyperlink
- specialization.
-
- Returns an integer representing the character offset of the
- embedded object character for this hyperlink in its parent's
- accessible text, or -1 something was amuck.
- """
-
- try:
- hyperlink = obj.queryHyperlink()
- except NotImplementedError:
- offset = -1
- else:
- # We need to make sure that this is an embedded object in
- # some accessible text (as opposed to an imagemap link).
- #
- try:
- obj.parent.queryText()
- except NotImplementedError:
- offset = -1
- else:
- offset = hyperlink.startIndex
-
- return offset
-
- def expandEOCs(self, obj, startOffset=0, endOffset=-1):
- """Expands the current object replacing EMBEDDED_OBJECT_CHARACTERS
- with their text.
-
- Arguments
- - obj: the object whose text should be expanded
- - startOffset: the offset of the first character to be included
- - endOffset: the offset of the last character to be included
-
- Returns the fully expanded text for the object.
- """
-
- if not obj:
- return None
-
- string = None
- try:
- text = obj.queryText()
- except:
- text = None
-
- if text and text.characterCount:
- string = text.getText(startOffset, endOffset)
- unicodeText = string.decode("UTF-8")
- if unicodeText \
- and self.EMBEDDED_OBJECT_CHARACTER in unicodeText:
- # If we're not getting the full text of this object, but
- # rather a substring, we need to figure out the offset of
- # the first child within this substring.
- #
- childOffset = 0
- for child in obj:
- if self.getCharacterOffsetInParent(child) >= startOffset:
- break
- childOffset += 1
-
- toBuild = list(unicodeText)
- count = toBuild.count(self.EMBEDDED_OBJECT_CHARACTER)
- for i in xrange(count):
- index = toBuild.index(self.EMBEDDED_OBJECT_CHARACTER)
- child = obj[i + childOffset]
- childText = self.expandEOCs(child)
- if not childText:
- childText = ""
- toBuild[index] = childText.decode("UTF-8")
- string = "".join(toBuild)
-
- return string
-
- def _getPronunciationForSegment(self, segment):
- """Adjust the word segment to potentially replace it with what
- those words actually sound like. Two pronunciation dictionaries
- are checked. First the application specific one (which might be
- empty), then the default (global) one.
-
- Arguments:
- - segment: the string to adjust for words in the pronunciation
- dictionaries.
-
- Returns: a new word segment adjusted for words found in the
- pronunciation dictionaries, or the original word segment if there
- was no dictionary entry.
- """
-
- newSegment = pronunciation_dict.getPronunciation(segment,
- self.app_pronunciation_dict)
- if newSegment == segment:
- newSegment = pronunciation_dict.getPronunciation(segment)
-
- return newSegment
-
- def adjustForPronunciation(self, line):
- """Adjust the line to replace words in the pronunciation dictionary,
- with what those words actually sound like.
-
- Arguments:
- - line: the string to adjust for words in the pronunciation dictionary.
-
- Returns: a new line adjusted for words found in the pronunciation
- dictionary.
- """
-
- newLine = ""
- words = self.WORDS_RE.split(line.decode("UTF-8"))
- for word in words:
- if word.isalnum():
- word = self._getPronunciationForSegment(word)
- newLine += word
-
- if line != newLine:
- debug.println(debug.LEVEL_FINEST,
- "adjustForPronunciation: \n From '%s'\n To '%s'" \
- % (line, newLine))
- return newLine.encode("UTF-8")
- else:
- return line
-
- def getLinkIndex(self, obj, characterIndex):
- """A brute force method to see if an offset is a link. This
- is provided because not all Accessible Hypertext implementations
- properly support the getLinkIndex method. Returns an index of
- 0 or greater of the characterIndex is on a hyperlink.
-
- Arguments:
- -obj: the Accessible object with the Accessible Hypertext specialization
- -characterIndex: the text position to check
- """
-
- if not obj:
- return -1
-
- try:
- obj.queryText()
- except NotImplementedError:
- return -1
-
- try:
- hypertext = obj.queryHypertext()
- except NotImplementedError:
- return -1
-
- for i in xrange(hypertext.getNLinks()):
- link = hypertext.getLink(i)
- if (characterIndex >= link.startIndex) \
- and (characterIndex <= link.endIndex):
- return i
-
- return -1
-
- def getCellIndex(self, obj):
- """Returns the index of the cell which should be used with the
- table interface. This is necessary because in some apps we
- cannot count on getIndexInParent() returning the index we need.
-
- Arguments:
- -obj: the table cell whose index we need.
- """
- return obj.getIndexInParent()
-
- 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
- normal end-of-sentence punctuation characters.
-
- Arguments:
- - currentChar: the current character
- - previousChar: the previous character
-
- Returns True if the given character is a sentence delimiter.
- """
-
- if not isinstance(currentChar, unicode):
- currentChar = currentChar.decode("UTF-8")
-
- if not isinstance(previousChar, unicode):
- previousChar = previousChar.decode("UTF-8")
-
- if currentChar == '\r' or currentChar == '\n':
- return True
-
- return (currentChar in self.whitespace and previousChar in '!.?:;')
-
- def isWordDelimiter(self, character):
- """Returns True if the given character is a word delimiter.
-
- Arguments:
- - character: the character in question
-
- Returns True if the given character is a word delimiter.
- """
-
- if not isinstance(character, unicode):
- character = character.decode("UTF-8")
-
- return (character in self.whitespace) \
- or (character in '!*+,-./:;<=>? [\]^_{|}') \
- or (character == self.NO_BREAK_SPACE_CHARACTER)
-
- def getFrame(self, obj):
- """Returns the frame containing this object, or None if this object
- is not inside a frame.
-
- Arguments:
- - obj: the Accessible object
- """
-
- debug.println(debug.LEVEL_FINEST,
- "Finding frame for source.name="
- + obj.name or "None")
-
- while obj \
- and (obj != obj.parent) \
- and (obj.getRole() != pyatspi.ROLE_FRAME):
- obj = obj.parent
- if obj:
- debug.println(debug.LEVEL_FINEST, "--> obj.name="
- + obj.name or "None")
-
- if obj and (obj.getRole() == pyatspi.ROLE_FRAME):
- pass
- else:
- obj = None
-
- return obj
-
- def getTopLevel(self, obj):
- """Returns the top-level object (frame, dialog ...) containing this
- object, or None if this object is not inside a top-level object.
-
- Arguments:
- - obj: the Accessible object
- """
-
- debug.println(debug.LEVEL_FINEST,
- "Finding top-level object for source.name="
- + obj.name or "None")
-
- while obj \
- and obj.parent \
- and (obj != obj.parent) \
- and (obj.parent.getRole() != pyatspi.ROLE_APPLICATION):
- obj = obj.parent
- debug.println(debug.LEVEL_FINEST, "--> obj.name="
- + obj.name or "None")
-
- if obj and obj.parent and \
- (obj.parent.getRole() == pyatspi.ROLE_APPLICATION):
- pass
- else:
- obj = None
-
- return obj
-
- def getTopLevelName(self, obj):
- """ Returns the name of the top-level object. See getTopLevel.
- """
- top = self.getTopLevel(obj)
- if (not top) or (not top.name):
- return ""
- else:
- return top.name
-
def getTextEndOffset(self, textInterface):
"""Returns the offset which should be used as the end offset.
By default, this is -1. However, this value triggers an assertion
@@ -7304,362 +5594,6 @@ class Script(script.Script):
return [content.encode("UTF-8"), text.caretOffset, startOffset]
- def getNestingLevel(self, obj):
- """Determines the nesting level of this object in a list. If this
- object is not in a list relation, then 0 will be returned.
-
- Arguments:
- -obj: the Accessible object
- """
-
- if not obj:
- return 0
-
- try:
- return self.generatorCache[self.NESTING_LEVEL][obj]
- except:
- if not self.generatorCache.has_key(self.NESTING_LEVEL):
- self.generatorCache[self.NESTING_LEVEL] = {}
-
- nestingLevel = 0
- parent = obj.parent
- while parent.parent.getRole() == pyatspi.ROLE_LIST:
- nestingLevel += 1
- parent = parent.parent
-
- self.generatorCache[self.NESTING_LEVEL][obj] = nestingLevel
- return self.generatorCache[self.NESTING_LEVEL][obj]
-
- def getNodeLevel(self, obj):
- """Determines the node level of this object if it is in a tree
- relation, with 0 being the top level node. If this object is
- not in a tree relation, then -1 will be returned.
-
- Arguments:
- -obj: the Accessible object
- """
-
- if not obj:
- return -1
-
- try:
- return self.generatorCache[self.NODE_LEVEL][obj]
- except:
- if not self.generatorCache.has_key(self.NODE_LEVEL):
- self.generatorCache[self.NODE_LEVEL] = {}
-
- nodes = []
- node = obj
- done = False
- while not done:
- relations = node.getRelationSet()
- node = None
- for relation in relations:
- if relation.getRelationType() \
- == pyatspi.RELATION_NODE_CHILD_OF:
- node = relation.getTarget(0)
- break
-
- # We want to avoid situations where something gives us an
- # infinite cycle of nodes. Bon Echo has been seen to do
- # this (see bug 351847).
- #
- if (len(nodes) > 100) or nodes.count(node):
- debug.println(debug.LEVEL_WARNING,
- "Script.getNodeLevel detected a cycle!!!")
- done = True
- elif node:
- nodes.append(node)
- debug.println(debug.LEVEL_FINEST,
- "Script.getNodeLevel %d" % len(nodes))
- else:
- done = True
-
- self.generatorCache[self.NODE_LEVEL][obj] = len(nodes) - 1
- return self.generatorCache[self.NODE_LEVEL][obj]
-
- def getChildNodes(self, obj):
- """Gets all of the children that have RELATION_NODE_CHILD_OF pointing
- to this expanded table cell.
-
- Arguments:
- -obj: the Accessible Object
-
- Returns: a list of all the child nodes
- """
-
- try:
- table = obj.parent.queryTable()
- except:
- return []
- else:
- if not obj.getState().contains(pyatspi.STATE_EXPANDED):
- return []
-
- nodes = []
-
- # First see if this accessible implements RELATION_NODE_PARENT_OF.
- # If it does, the full target list are the nodes. If it doesn't
- # we'll do an old-school, row-by-row search for child nodes.
- #
- relations = obj.getRelationSet()
- try:
- for relation in relations:
- if relation.getRelationType() == \
- pyatspi.RELATION_NODE_PARENT_OF:
- for target in range(relation.getNTargets()):
- nodes.append(relation.getTarget(target))
- return nodes
- except:
- pass
-
- index = self.getCellIndex(obj)
- row = table.getRowAtIndex(index)
- col = table.getColumnAtIndex(index)
- nodeLevel = self.getNodeLevel(obj)
- done = False
-
- # Candidates will be in the rows beneath the current row.
- # Only check in the current column and stop checking as
- # soon as the node level of a candidate is equal or less
- # than our current level.
- #
- for i in range(row+1, table.nRows):
- cell = table.getAccessibleAt(i, col)
- relations = cell.getRelationSet()
- for relation in relations:
- if relation.getRelationType() \
- == pyatspi.RELATION_NODE_CHILD_OF:
- nodeOf = relation.getTarget(0)
- if self.isSameObject(obj, nodeOf):
- nodes.append(cell)
- else:
- currentLevel = self.getNodeLevel(nodeOf)
- if currentLevel <= nodeLevel:
- done = True
- break
- if done:
- break
-
- return nodes
-
- def getKeyBinding(self, obj):
- """Gets the mnemonic, accelerator string and possibly shortcut
- for the given object. These are based upon the first accessible
- action for the object.
-
- Arguments:
- - obj: the Accessible object
-
- Returns: list containing strings: [mnemonic, shortcut, accelerator]
- """
-
- try:
- return self.generatorCache[self.KEY_BINDING][obj]
- except:
- if not self.generatorCache.has_key(self.KEY_BINDING):
- self.generatorCache[self.KEY_BINDING] = {}
-
- try:
- action = obj.queryAction()
- except NotImplementedError:
- self.generatorCache[self.KEY_BINDING][obj] = ["", "", ""]
- return self.generatorCache[self.KEY_BINDING][obj]
-
- # Action is a string in the format, where the mnemonic and/or
- # accelerator can be missing.
- #
- # <mnemonic>;<full-path>;<accelerator>
- #
- # The keybindings in <full-path> should be separated by ":"
- #
- bindingStrings = action.getKeyBinding(0).decode("UTF-8").split(';')
-
- if len(bindingStrings) == 3:
- mnemonic = bindingStrings[0]
- fullShortcut = bindingStrings[1]
- accelerator = bindingStrings[2]
- elif len(bindingStrings) > 0:
- mnemonic = ""
- fullShortcut = bindingStrings[0]
- try:
- accelerator = bindingStrings[1]
- except:
- accelerator = ""
- else:
- mnemonic = ""
- fullShortcut = ""
- accelerator = ""
-
- fullShortcut = fullShortcut.replace("<","")
- fullShortcut = fullShortcut.replace(">"," ")
- fullShortcut = fullShortcut.replace(":"," ").strip()
-
- # If the accelerator or mnemonic strings includes a Space,
- # make sure we speak it.
- #
- if mnemonic.endswith(" "):
- # Translators: this is the spoken word for the space character
- #
- mnemonic += _("space")
- mnemonic = mnemonic.replace("<","")
- mnemonic = mnemonic.replace(">"," ").strip()
-
- if accelerator.endswith(" "):
- # Translators: this is the spoken word for the space character
- #
- accelerator += _("space")
- accelerator = accelerator.replace("<","")
- accelerator = accelerator.replace(">"," ").strip()
-
- debug.println(debug.LEVEL_FINEST, "default.getKeyBinding: " \
- + repr([mnemonic, fullShortcut, accelerator]))
-
- self.generatorCache[self.KEY_BINDING][obj] = \
- [mnemonic, fullShortcut, accelerator]
- return self.generatorCache[self.KEY_BINDING][obj]
-
- def getKnownApplications(self):
- """Retrieves the list of currently running apps for the desktop
- as a list of Accessible objects.
- """
-
- debug.println(debug.LEVEL_FINEST,
- "Script.getKnownApplications...")
-
- apps = filter(lambda x: x is not None,
- pyatspi.Registry.getDesktop(0))
-
- debug.println(debug.LEVEL_FINEST,
- "...Script.getKnownApplications")
-
- return apps
-
- def getObjects(self, root, onlyShowing=True):
- """Returns a list of all objects under the given root. Objects
- are returned in no particular order - this function does a simple
- tree traversal, ignoring the children of objects which report the
- MANAGES_DESCENDANTS state.
-
- Arguments:
- - root: the Accessible object to traverse
- - onlyShowing: examine only those objects that are SHOWING
-
- Returns: a list of all objects under the specified object
- """
-
- # The list of object we'll return
- #
- objlist = []
-
- # Start at the first child of the given object
- #
- if root.childCount <= 0:
- return objlist
-
- for i, child in enumerate(root):
- debug.println(debug.LEVEL_FINEST,
- "Script.getObjects looking at child %d" % i)
- if child \
- and ((not onlyShowing) or (onlyShowing and \
- (child.getState().contains(pyatspi.STATE_SHOWING)))):
- objlist.append(child)
- if (child.getState().contains( \
- pyatspi.STATE_MANAGES_DESCENDANTS) == 0) \
- and (child.childCount > 0):
- objlist.extend(self.getObjects(child, onlyShowing))
-
- return objlist
-
- def findByRole(self, root, role, onlyShowing=True):
- """Returns a list of all objects of a specific role beneath the
- given root. [[[TODO: MM - This is very inefficient - this should
- do it's own traversal and not add objects to the list that aren't
- of the specified role. Instead it uses the traversal from
- getObjects and then deletes objects from the list that aren't of
- the specified role. Logged as bugzilla bug 319740.]]]
-
- Arguments:
- - root the Accessible object to traverse
- - role the string describing the Accessible role of the object
- - onlyShowing: examine only those objects that are SHOWING
-
- Returns a list of descendants of the root that are of the given role.
- """
-
- objlist = []
- allobjs = self.getObjects(root, onlyShowing)
- for o in allobjs:
- if o.getRole() == role:
- objlist.append(o)
- return objlist
-
- def findUnrelatedLabels(self, root):
- """Returns a list containing all the unrelated (i.e., have no
- relations to anything and are not a fundamental element of a
- more atomic component like a combo box) labels under the given
- root. Note that the labels must also be showing on the display.
-
- Arguments:
- - root the Accessible object to traverse
-
- Returns a list of unrelated labels under the given root.
- """
-
- # Find all the labels in the dialog
- #
- allLabels = self.findByRole(root, pyatspi.ROLE_LABEL)
-
- # add the names of only those labels which are not associated with
- # other objects (i.e., empty relation sets).
- #
- # [[[WDW - HACK: In addition, do not grab free labels whose
- # parents are push buttons because push buttons can have labels as
- # children.]]]
- #
- # [[[WDW - HACK: panels with labelled borders will have a child
- # label that does not have its relation set. So...we check to see
- # if the panel's name is the same as the label's name. If so, we
- # ignore the label.]]]
- #
- unrelatedLabels = []
-
- for label in allLabels:
- relations = label.getRelationSet()
- if len(relations) == 0:
- parent = label.parent
- if parent and (parent.getRole() == pyatspi.ROLE_PUSH_BUTTON):
- pass
- elif parent and (parent.getRole() == pyatspi.ROLE_PANEL) \
- and (parent.name == label.name):
- pass
- elif label.getState().contains(pyatspi.STATE_SHOWING):
- unrelatedLabels.append(label)
-
- # Now sort the labels based on their geographic position, top to
- # bottom, left to right. This is a very inefficient sort, but the
- # assumption here is that there will not be a lot of labels to
- # worry about.
- #
- sortedLabels = []
- for label in unrelatedLabels:
- label_extents = \
- label.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
- index = 0
- for sortedLabel in sortedLabels:
- sorted_extents = \
- sortedLabel.queryComponent().getExtents(
- pyatspi.DESKTOP_COORDS)
- if (label_extents.y > sorted_extents.y) \
- or ((label_extents.y == sorted_extents.y) \
- and (label_extents.x > sorted_extents.x)):
- index += 1
- else:
- break
- sortedLabels.insert(index, label)
-
- return sortedLabels
-
def phoneticSpellCurrentItem(self, itemString):
"""Phonetically spell the current flat review word or line.
@@ -7676,170 +5610,6 @@ class Script(script.Script):
phoneticString = phonnames.getPhoneticName(character)
speech.speak(phoneticString, voice)
- def printAncestry(self, child):
- """Prints a hierarchical view of a child's ancestry."""
-
- if not child:
- return
-
- ancestorList = [child]
- parent = child.parent
- while parent and (parent.parent != parent):
- ancestorList.insert(0, parent)
- parent = parent.parent
-
- indent = ""
- for ancestor in ancestorList:
- line = indent + "+- " + debug.getAccessibleDetails(ancestor)
- debug.println(debug.LEVEL_OFF, line)
- print line
- indent += " "
-
- def printHierarchy(self, root, ooi, indent="",
- onlyShowing=True, omitManaged=True):
- """Prints the accessible hierarchy of all children
-
- Arguments:
- -indent: Indentation string
- -root: Accessible where to start
- -ooi: Accessible object of interest
- -onlyShowing: If True, only show children painted on the screen
- -omitManaged: If True, omit children that are managed descendants
- """
-
- if not root:
- return
-
- if root == ooi:
- line = indent + "(*) " + debug.getAccessibleDetails(root)
- else:
- line = indent + "+- " + debug.getAccessibleDetails(root)
-
- debug.println(debug.LEVEL_OFF, line)
- print line
-
- rootManagesDescendants = root.getState().contains( \
- pyatspi.STATE_MANAGES_DESCENDANTS)
-
- for child in root:
- if child == root:
- line = indent + " " + "WARNING CHILD == PARENT!!!"
- debug.println(debug.LEVEL_OFF, line)
- print line
- elif not child:
- line = indent + " " + "WARNING CHILD IS NONE!!!"
- debug.println(debug.LEVEL_OFF, line)
- print line
- elif child.parent != root:
- line = indent + " " + "WARNING CHILD.PARENT != PARENT!!!"
- debug.println(debug.LEVEL_OFF, line)
- print line
- else:
- paint = (not onlyShowing) or (onlyShowing and \
- child.getState().contains(pyatspi.STATE_SHOWING))
- paint = paint \
- and ((not omitManaged) \
- or (omitManaged and not rootManagesDescendants))
-
- if paint:
- self.printHierarchy(child,
- ooi,
- indent + " ",
- onlyShowing,
- omitManaged)
-
- def printApps(self):
- """Prints a list of all applications to stdout."""
-
- apps = self.getKnownApplications()
- line = "There are %d Accessible applications" % len(apps)
- debug.println(debug.LEVEL_OFF, line)
- print line
- for app in apps:
- line = debug.getAccessibleDetails(app, " App: ", False)
- debug.println(debug.LEVEL_OFF, line)
- print line
- for child in app:
- line = debug.getAccessibleDetails(child, " Window: ", False)
- debug.println(debug.LEVEL_OFF, line)
- print line
- if child.parent != app:
- debug.println(debug.LEVEL_OFF,
- " WARNING: child's parent is not app!!!")
-
- return True
-
- def isInActiveApp(self, obj):
- """Returns True if the given object is from the same application that
- currently has keyboard focus.
-
- Arguments:
- - obj: an Accessible object
- """
-
- if not obj:
- return False
- else:
- return orca_state.locusOfFocus \
- and (orca_state.locusOfFocus.getApplication() \
- == obj.getApplication())
-
- def findActiveWindow(self):
- """Traverses the list of known apps looking for one who has an
- immediate child (i.e., a window) whose state includes the active state.
-
- Returns the Python Accessible of the window that's active or None if
- no windows are active.
- """
-
- window = None
- apps = self.getKnownApplications()
- for app in apps:
- for child in app:
- try:
- state = child.getState()
- if state.contains(pyatspi.STATE_ACTIVE):
- window = child
- break
- except:
- debug.printException(debug.LEVEL_FINEST)
-
- return window
-
- def getAncestor(self, obj, ancestorRoles, stopRoles):
- """Returns the object of the specified roles which contains the
- given object, or None if the given object is not contained within
- an object the specified roles.
-
- Arguments:
- - obj: the Accessible object
- - ancestorRoles: the list of roles to look for
- - stopRoles: the list of roles to stop the search at
- """
-
- if not obj:
- return None
-
- if not isinstance(ancestorRoles, [].__class__):
- ancestorRoles = [ancestorRoles]
-
- if not isinstance(stopRoles, [].__class__):
- stopRoles = [stopRoles]
-
- ancestor = None
-
- obj = obj.parent
- while obj and (obj != obj.parent):
- if obj.getRole() in ancestorRoles:
- ancestor = obj
- break
- elif obj.getRole() in stopRoles:
- break
- else:
- obj = obj.parent
-
- return ancestor
-
def saveOldAppSettings(self):
"""Save a copy of all the existing application specific settings
(as specified by the settings.userCustomizableSettings dictionary)."""
@@ -7857,68 +5627,6 @@ class Script(script.Script):
if key in prefsDict:
setattr(settings, key, prefsDict[key])
- ########################################################################
- # #
- # METHODS FOR DRAWING RECTANGLES AROUND OBJECTS ON THE SCREEN #
- # #
- ########################################################################
-
- def drawOutline(self, x, y, width, height):
- """Draws an outline around the accessible, erasing the
- last drawn outline in the process."""
-
- if (x == -1) and (y == 0) and (width == 0) and (height == 0):
- outline.erase()
- else:
- outline.draw(x, y, width, height)
-
- def outlineAccessible(self, accessible):
- """Draws a rectangular outline around the accessible, erasing the
- last drawn rectangle in the process."""
-
- try:
- component = accessible.queryComponent()
- except AttributeError:
- self.drawOutline(-1, 0, 0, 0)
- except NotImplementedError:
- pass
- else:
- visibleRectangle = \
- component.getExtents(pyatspi.DESKTOP_COORDS)
- self.drawOutline(visibleRectangle.x, visibleRectangle.y,
- visibleRectangle.width, visibleRectangle.height)
-
- def isTextSelected(self, obj, startOffset, endOffset):
- """Returns an indication of whether the text is selected by
- comparing the text offset with the various selected regions of
- text for this accessible object.
-
- Arguments:
- - obj: the Accessible object.
- - startOffset: text start offset.
- - endOffset: text end offset.
-
- Returns an indication of whether the text is selected.
- """
-
- # If start offset and end offset are the same, just return False.
- # This is possible if there was no text spoken in onCaretMoved.
- #
- if startOffset == endOffset:
- return False
-
- try:
- text = obj.queryText()
- except:
- return False
-
- for i in xrange(text.getNSelections()):
- [startSelOffset, endSelOffset] = text.getSelection(i)
- if (startOffset >= startSelOffset) and (endOffset <= endSelOffset):
- return True
-
- return False
-
def _saveSpokenTextRange(self, startOffset, endOffset):
"""Save away the start and end offset of the range of text that
was spoken. It will be used by speakTextSelectionState, to try
@@ -8142,14 +5850,14 @@ class Script(script.Script):
#
if n > 1:
while endOffset > startOffset:
- if self.isWordDelimiter(tmpStr[n-1]):
+ if self.utilities.isWordDelimiter(tmpStr[n-1]):
n -= 1
endOffset -= 1
else:
break
n = 0
while startOffset < endOffset:
- if self.isWordDelimiter(tmpStr[n]):
+ if self.utilities.isWordDelimiter(tmpStr[n]):
n += 1
startOffset += 1
else:
@@ -8158,7 +5866,7 @@ class Script(script.Script):
except:
debug.printException(debug.LEVEL_FINEST)
- if self.isTextSelected(obj, startOffset, endOffset):
+ if self.utilities.isTextSelected(obj, startOffset, endOffset):
# Translators: when the user selects (highlights) text in
# a document, Orca lets them know this.
#
@@ -8172,280 +5880,13 @@ class Script(script.Script):
self._saveLastTextSelections(text)
- def getURI(self, obj):
- """Return the URI for a given link object.
-
- Arguments:
- - obj: the Accessible object.
- """
- return obj.queryHyperlink().getURI(0)
-
- def getDocumentFrame(self):
- """Dummy method used as a reminder to refactor whereamI for links,
- possibly subclassing whereamI for the Gecko script.
- """
- return None
-
def systemBeep(self):
- """Rings the system bell. This is really a hack. Ideally, we want
- a method that will present an earcon (any sound designated representing
- an error, event etc)
- """
- print "\a"
-
- def clearTextSelection(self, obj):
- """Clears the text selection if the object supports it."""
- try:
- texti = obj.queryText()
- except:
- return
-
- for i in range(0, texti.getNSelections()):
- texti.removeSelection(0)
-
- def adjustTextSelection(self, obj, offset):
- """Adjusts the end point of a text selection"""
- try:
- texti = obj.queryText()
- except:
- return
-
- if not texti.getNSelections():
- caretOffset = texti.caretOffset
- startOffset = min(offset, caretOffset)
- endOffset = max(offset, caretOffset)
- texti.addSelection(startOffset, endOffset)
- else:
- startOffset, endOffset = texti.getSelection(0)
- if offset < startOffset:
- startOffset = offset
- else:
- endOffset = offset
- texti.setSelection(0, startOffset, endOffset)
-
- def setCaretOffset(self, obj, offset):
- """Set the caret offset on a given accessible. Similar to
- Accessible.setCaretOffset()
-
- Arguments:
- - obj: Given accessible object.
- - offset: Offset to hich to set the caret.
- """
- try:
- texti = obj.queryText()
- except:
- return None
-
- texti.setCaretOffset(offset)
-
- def attributeStringToDictionary(self, dict_string):
- """Creates a Python dict from a typical attributes list returned from
- different AT-SPI methods.
-
- Arguments:
- - dict_string: A list of colon seperated key/value pairs seperated by
- semicolons.
- Returns a Python dict of the given attributes.
- """
- try:
- return dict(
- map(lambda pair: pair.strip().split(':'),
- dict_string.strip('; ').split(';')))
- except ValueError:
- return {}
-
- def _getPopupItemAtDesktopCoords(self, x, y):
- """Since pop-up items often don't contain nested components, we need
- a way to efficiently determine if the cursor is over a menu item.
-
- Arguments:
- - x: X coordinate.
- - y: Y coordinate.
-
- Returns a menu item the mouse is over, or None.
- """
- suspect_children = []
- if self.lastSelectedMenu:
- try:
- si = self.lastSelectedMenu.querySelection()
- except NotImplementedError:
- return None
-
- if si.nSelectedChildren > 0:
- suspect_children = [si.getSelectedChild(0)]
- else:
- suspect_children = self.lastSelectedMenu
- for child in suspect_children:
- try:
- ci = child.queryComponent()
- except NotImplementedError:
- continue
-
- if ci.contains(x, y, pyatspi.DESKTOP_COORDS) \
- and ci.getLayer() == pyatspi.LAYER_POPUP:
- return child
-
- def getComponentAtDesktopCoords(self, parent, x, y):
- """Get the descendant component at the given desktop coordinates.
-
- Arguments:
-
- - parent: The parent component we are searching below.
- - x: X coordinate.
- - y: Y coordinate.
-
- Returns end-node that contains the given coordinates, or None.
- """
- acc = self._getPopupItemAtDesktopCoords(x, y)
- if acc:
- return acc
-
- container = parent
- while True:
- if container.getRole() == pyatspi.ROLE_PAGE_TAB_LIST:
- try:
- si = container.querySelection()
- container = si.getSelectedChild(0)[0]
- except NotImplementedError:
- pass
- try:
- ci = container.queryComponent()
- except:
- return None
- else:
- inner_container = container
- container = ci.getAccessibleAtPoint(x, y, pyatspi.DESKTOP_COORDS)
- if not container or container.queryComponent() == ci:
- # The gecko bridge simply has getAccessibleAtPoint return
- # itself if there are no further children.
- # TODO: Put in Gecko.py
- break
- if inner_container == parent:
- return None
- else:
- return inner_container
-
- # pylint: disable-msg=W0142
-
- def getSelectedText(self, obj):
- """Get the text selection for the given object.
-
- Arguments:
- - obj: the text object to extract the selected text from.
-
- Returns: the selected text contents plus the start and end
- offsets within the text.
- """
-
- textContents = ""
- textObj = obj.queryText()
- nSelections = textObj.getNSelections()
- for i in range(0, nSelections):
- [startOffset, endOffset] = textObj.getSelection(i)
-
- debug.println(debug.LEVEL_FINEST,
- "getSelectedText: selection start=%d, end=%d" % \
- (startOffset, endOffset))
-
- selectedText = textObj.getText(startOffset, endOffset)
- debug.println(debug.LEVEL_FINEST,
- "getSelectedText: selected text=<%s>" % selectedText)
-
- if i > 0:
- textContents += " "
- textContents += selectedText
-
- return [textContents, startOffset, endOffset]
-
- def getAllSelectedText(self, obj):
- """Get all the text applicable text selections for the given object.
- including any previous or next text objects that also have
- selected text and add in their text contents.
-
- Arguments:
- - obj: the text object to start extracting the selected text from.
-
- Returns: all the selected text contents plus the start and end
- offsets within the text for the given object.
- """
-
- textContents = ""
- startOffset = 0
- endOffset = 0
- text = obj.queryText()
- if text.getNSelections() > 0:
- [textContents, startOffset, endOffset] = \
- self.getSelectedText(obj)
-
- current = obj
- morePossibleSelections = True
- while morePossibleSelections:
- morePossibleSelections = False
- for relation in current.getRelationSet():
- if relation.getRelationType() \
- == pyatspi.RELATION_FLOWS_FROM:
- prevObj = relation.getTarget(0)
- prevObjText = prevObj.queryText()
- if prevObjText.getNSelections() > 0:
- [newTextContents, start, end] = \
- self.getSelectedText(prevObj)
- textContents = newTextContents + " " + textContents
- current = prevObj
- morePossibleSelections = True
- else:
- displayedText = prevObjText.getText(0,
- self.getTextEndOffset(prevObjText))
- if len(displayedText) == 0:
- current = prevObj
- morePossibleSelections = True
- break
-
- current = obj
- morePossibleSelections = True
- while morePossibleSelections:
- morePossibleSelections = False
- for relation in current.getRelationSet():
- if relation.getRelationType() \
- == pyatspi.RELATION_FLOWS_TO:
- nextObj = relation.getTarget(0)
- nextObjText = nextObj.queryText()
- if nextObjText.getNSelections() > 0:
- [newTextContents, start, end] = \
- self.getSelectedText(nextObj)
- textContents += " " + newTextContents
- current = nextObj
- morePossibleSelections = True
- else:
- displayedText = nextObjText.getText(0,
- self.getTextEndOffset(nextObjText))
- if len(displayedText) == 0:
- current = nextObj
- morePossibleSelections = True
- break
-
- return [textContents, startOffset, endOffset]
-
- def getAllTextSelections(self, acc):
- """Get a list of text selections in the given accessible object,
- equivelent to getNSelections()*texti.getSelection()
-
- Arguments:
- - acc: An accessible.
-
- Returns list of start and end offsets for multiple selections, or an
- empty list if nothing is selected or if the accessible does not support
- the text interface.
+ """Rings the system bell. This is really a hack. Ideally, we want
+ a method that will present an earcon (any sound designated for the
+ purpose of representing an error, event etc)
"""
- rv = []
- try:
- texti = acc.queryText()
- except:
- return rv
- for i in xrange(texti.getNSelections()):
- rv.append(texti.getSelection(i))
-
- return rv
+ print "\a"
def speakWordUnderMouse(self, acc):
"""Determine if the speak-word-under-mouse capability applies to
@@ -8477,13 +5918,13 @@ class Script(script.Script):
charAndOffsets = \
text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_CHAR)
if not charAndOffsets[0].strip() \
- or self.isWordDelimiter(charAndOffsets[0]):
+ or self.utilities.isWordDelimiter(charAndOffsets[0]):
orca_state.lastWordCheckedForSpelling = charAndOffsets[0]
return
wordAndOffsets = \
text.getTextAtOffset(offset, pyatspi.TEXT_BOUNDARY_WORD_START)
- if self.isWordMisspelled(obj, offset) \
+ if self.utilities.isWordMisspelled(obj, offset) \
and wordAndOffsets[0] != orca_state.lastWordCheckedForSpelling:
# Translators: this is to inform the user of the presence
# of the red squiggly line which indicates that a given
@@ -8496,108 +5937,6 @@ class Script(script.Script):
#
orca_state.lastWordCheckedForSpelling = wordAndOffsets[0]
- def isWordMisspelled(self, obj, offset):
- """Identifies if the current word is flagged as misspelled by the
- application.
-
- Arguments:
- - obj: An accessible which implements the accessible text interface.
- - offset: Offset in the accessible's text for which to retrieve the
- attributes.
-
- Returns True if the word is flagged as misspelled.
- """
-
- # Right now, the Gecko toolkit is the only one to expose this
- # information to us. As other appliations and toolkits do so,
- # the scripts for those applications/toolkits can override this
- # method and, theoretically, the presentation of misspelled words
- # should JustWork(tm).
- #
- return False
-
- def getTextAttributes(self, acc, offset, get_defaults=False):
- """Get the text attributes run for a given offset in a given accessible
-
- Arguments:
- - acc: An accessible.
- - offset: Offset in the accessible's text for which to retrieve the
- attributes.
- - get_defaults: Get the default attributes as well as the unique ones.
- Default is True
-
- Returns a dictionary of attributes, a start offset where the attributes
- begin, and an end offset. Returns ({}, 0, 0) if the accessible does not
- supprt the text attribute.
- """
- rv = {}
- try:
- texti = acc.queryText()
- except:
- return rv, 0, 0
-
- if get_defaults:
- rv.update(self.attributeStringToDictionary(
- texti.getDefaultAttributes()))
-
- attrib_str, start, end = texti.getAttributes(offset)
-
- rv.update(self.attributeStringToDictionary(attrib_str))
-
- return rv, start, end
-
- def getWordAtCoords(self, acc, x, y):
- """Get the word at the given coords in the accessible.
-
- Arguments:
- - acc: Accessible that supports the Text interface.
- - x: X coordinate.
- - y: Y coordinate.
-
- Returns a tuple containing the word, start offset, and end offset.
- """
- try:
- ti = acc.queryText()
- except NotImplementedError:
- return '', 0, 0
-
- text_contents = ti.getText(0, self.getTextEndOffset(ti))
- line_offsets = []
- start_offset = 0
- while True:
- try:
- end_offset = text_contents.index('\n', start_offset)
- except ValueError:
- line_offsets.append((start_offset, len(text_contents)))
- break
- line_offsets.append((start_offset, end_offset))
- start_offset = end_offset + 1
- for start, end in line_offsets:
- bx, by, bw, bh = \
- ti.getRangeExtents(start, end, pyatspi.DESKTOP_COORDS)
- bb = mouse_review.BoundingBox(bx, by, bw, bh)
- if bb.isInBox(x, y):
- start_offset = 0
- word_offsets = []
- while True:
- try:
- end_offset = \
- text_contents[start:end].index(' ', start_offset)
- except ValueError:
- word_offsets.append((start_offset,
- len(text_contents[start:end])))
- break
- word_offsets.append((start_offset, end_offset))
- start_offset = end_offset + 1
- for a, b in word_offsets:
- bx, by, bw, bh = \
- ti.getRangeExtents(start+a, start+b,
- pyatspi.DESKTOP_COORDS)
- bb = mouse_review.BoundingBox(bx, by, bw, bh)
- if bb.isInBox(x, y):
- return text_contents[start+a:start+b], start+a, start+b
- return '', 0, 0
-
########################################################################
# #
# Braille methods #
@@ -8925,6 +6264,13 @@ class Script(script.Script):
########################################################################
@staticmethod
+ def speakMessage(string):
+ """Method to speak a single string. Scripts should use this
+ method rather than calling speech.speak directly."""
+
+ speech.speak(string)
+
+ @staticmethod
def presentItemsInSpeech(items):
"""Method to speak a list of items. Scripts should call this
method rather than handling the creation and speaking of
@@ -8940,6 +6286,21 @@ class Script(script.Script):
speech.speak(utterances)
+ def speakUnicodeCharacter(self, character):
+ """ Speaks some information about an unicode character.
+ At the Momment it just anounces the character unicode number but
+ this information may be changed in the future
+
+ Arguments:
+ - character: the character to speak information of
+ """
+ # Translators: this is information about a unicode character
+ # reported to the user. The value is the unicode number value
+ # of this character in hex.
+ #
+ speech.speak(_("Unicode %s") % \
+ self.utilities.unicodeValueString(character))
+
# Dictionary that defines the state changes we care about for various
# objects. The key represents the role and the value represents a list
# of states that we care about.
diff --git a/src/orca/flat_review.py b/src/orca/flat_review.py
index ba173d7..15968ba 100644
--- a/src/orca/flat_review.py
+++ b/src/orca/flat_review.py
@@ -628,8 +628,8 @@ class Context:
if orca_state.locusOfFocus and \
orca_state.locusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL:
- searchZone = orca_state.activeScript.getRealActiveDescendant(\
- orca_state.locusOfFocus)
+ searchZone = orca_state.activeScript.\
+ utilities.realActiveDescendant(orca_state.locusOfFocus)
else:
searchZone = orca_state.locusOfFocus
@@ -639,7 +639,8 @@ class Context:
currentZoneIndex = 0
while currentZoneIndex < len(line.zones):
zone = line.zones[currentZoneIndex]
- if self.script.isSameObject(zone.accessible, searchZone):
+ if self.script.utilities.isSameObject(
+ zone.accessible, searchZone):
foundZoneWithFocus = True
break
else:
@@ -784,11 +785,12 @@ class Context:
substringEndOffset = substringStartOffset
unicodeStartOffset = i + 1
else:
- [x, y, width, height] = text.getRangeExtents( \
- substringStartOffset, substringEndOffset, 0)
- if self.script.visible(x, y, width, height,
- cliprect.x, cliprect.y,
- cliprect.width, cliprect.height):
+ [x, y, width, height] = text.getRangeExtents(
+ substringStartOffset, substringEndOffset, 0)
+ if self.script.utilities.isVisibleRegion(
+ x, y, width, height,
+ cliprect.x, cliprect.y,
+ cliprect.width, cliprect.height):
anyVisible = True
@@ -1096,10 +1098,11 @@ class Context:
#
extents = icomponent.getExtents(0)
- if not self.script.visible(extents.x, extents.y,
- extents.width, extents.height,
- cliprect.x, cliprect.y,
- cliprect.width, cliprect.height):
+ if not self.script.utilities.isVisibleRegion(
+ extents.x, extents.y,
+ extents.width, extents.height,
+ cliprect.x, cliprect.y,
+ cliprect.width, cliprect.height):
return []
debug.println(
@@ -1144,10 +1147,11 @@ class Context:
[x, y] = iimage.getImagePosition(0)
[width, height] = iimage.getImageSize()
- if (width != 0) and (height != 0) \
- and self.script.visible(x, y, width, height,
- cliprect.x, cliprect.y,
- cliprect.width, cliprect.height):
+ if width != 0 and height != 0 \
+ and self.script.utilities.isVisibleRegion(
+ x, y, width, height,
+ cliprect.x, cliprect.y,
+ cliprect.width, cliprect.height):
clipping = self.clip(x, y, width, height,
cliprect.x, cliprect.y,
@@ -1200,7 +1204,7 @@ class Context:
try:
selection = accessible[0].querySelection()
except:
- string = self.script.getDisplayedText(accessible[0])
+ string = self.script.utilities.displayedText(accessible[0])
else:
item = selection.getSelectedChild(0)
if item:
@@ -1311,7 +1315,7 @@ class Context:
pass
showingDescendants = \
- self.script.getShowingDescendants(root)
+ self.script.utilities.showingDescendants(root)
if len(showingDescendants):
for child in showingDescendants:
zones.extend(self.getShowingZones(child))
@@ -1333,7 +1337,7 @@ class Context:
"flat_review.getShowingZones: " +
"WARNING CHILD.PARENT != PARENT!!!")
- if self.script.pursueForFlatReview(child):
+ if self.script.utilities.pursueForFlatReview(child):
zones.extend(self.getShowingZones(child))
return zones
diff --git a/src/orca/focus_tracking_presenter.py b/src/orca/focus_tracking_presenter.py
index 83268be..5a0b6ee 100644
--- a/src/orca/focus_tracking_presenter.py
+++ b/src/orca/focus_tracking_presenter.py
@@ -1089,7 +1089,7 @@ class FocusTrackingPresenter(presentation_manager.PresentationManager):
self._registerEventListener("window:deactivate")
self._registerEventListener("object:children-changed:remove")
- win = orca_state.activeScript.findActiveWindow()
+ win = orca_state.activeScript.utilities.activeWindow()
if win:
# Generate a fake window activation event so the application
# can tell the user about itself.
diff --git a/src/orca/generator.py b/src/orca/generator.py
index 1b9d4a7..9dd74e1 100644
--- a/src/orca/generator.py
+++ b/src/orca/generator.py
@@ -287,7 +287,7 @@ class Generator:
needed a _generateDescription for whereAmI. :-) See below.
"""
result = []
- name = self._script.getDisplayedText(obj)
+ name = self._script.utilities.displayedText(obj)
if name:
result.append(name)
elif obj.description:
@@ -327,19 +327,19 @@ class Generator:
is different from that of the name and label.
"""
result = []
- label = self._script.getDisplayedLabel(obj)
+ label = self._script.utilities.displayedLabel(obj)
if obj.description and not obj.description in [obj.name, label]:
result.append(obj.description)
return result
def _generateLabel(self, obj, **args):
"""Returns the label for an object as an array of strings for use by
- speech and braille. The label is determined by the
- getDisplayedLabel of the script, and an empty array will be
- returned if no label can be found.
+ speech and braille. The label is determined by the displayedLabel
+ method of the script utility, and an empty array will be returned if
+ no label can be found.
"""
result = []
- label = self._script.getDisplayedLabel(obj)
+ label = self._script.utilities.displayedLabel(obj)
if label:
result.append(label)
return result
@@ -413,7 +413,7 @@ class Generator:
args['mode'] = self._mode
args['stringType'] = 'readonly'
if settings.presentReadOnlyText \
- and self._script.isReadOnlyTextArea(obj):
+ and self._script.utilities.isReadOnlyTextArea(obj):
result.append(self._script.formatting.getString(**args))
return result
@@ -560,7 +560,7 @@ class Generator:
except:
pass
else:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
rowIndex = table.getRowAtIndex(index)
if rowIndex >= 0:
# Get the header information. In Java Swing, the
@@ -578,7 +578,7 @@ class Generator:
if not desc:
header = table.getRowHeader(rowIndex)
if header:
- desc = self._script.getDisplayedText(header)
+ desc = self._script.utilities.displayedText(header)
if desc and len(desc):
text = desc
if args['mode'] == 'speech':
@@ -621,7 +621,7 @@ class Generator:
except:
pass
else:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
columnIndex = table.getColumnAtIndex(index)
if columnIndex >= 0:
# Get the header information. In Java Swing, the
@@ -639,7 +639,7 @@ class Generator:
if not desc:
header = table.getColumnHeader(columnIndex)
if header:
- desc = self._script.getDisplayedText(header)
+ desc = self._script.utilities.displayedText(header)
if desc and len(desc):
text = desc
if args['mode'] == 'speech':
@@ -765,13 +765,13 @@ class Generator:
return result
try:
action = obj.queryAction()
- label = self._script.getDisplayedText(
- self._script.getRealActiveDescendant(obj))
+ label = self._script.utilities.displayedText(
+ self._script.utilities.realActiveDescendant(obj))
except NotImplementedError:
action = None
label = None
if action and (label == None or len(label) == 0):
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
column = parentTable.getColumnAtIndex(index)
for j in range(0, action.nActions):
# Translators: this is the action name for
@@ -816,9 +816,9 @@ class Generator:
parentTable = None
isDetailedWhereAmI = args.get('formatType', None) == 'detailedWhereAmI'
if (settings.readTableCellRow or isDetailedWhereAmI) and parentTable \
- and (not self._script.isLayoutOnly(obj.parent)):
+ and (not self._script.utilities.isLayoutOnly(obj.parent)):
parent = obj.parent
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
row = parentTable.getRowAtIndex(index)
column = parentTable.getColumnAtIndex(index)
@@ -881,7 +881,7 @@ class Generator:
- consider returning an empty array if this is not a text
object.]]]
"""
- return [self._script.getDisplayedText(obj)]
+ return [self._script.utilities.displayedText(obj)]
#####################################################################
# #
@@ -898,7 +898,7 @@ class Generator:
if not args.get('mode', None):
args['mode'] = self._mode
args['stringType'] = 'nodelevel'
- level = self._script.getNodeLevel(obj)
+ level = self._script.utilities.nodeLevel(obj)
if level >= 0:
result.append(self._script.formatting.getString(**args)\
% (level + 1))
@@ -917,7 +917,7 @@ class Generator:
attribute if it exists on the object. [[[WDW - we should
consider returning an empty array if there is no value.
"""
- return [self._script.getTextForValue(obj)]
+ return [self._script.utilities.textForValue(obj)]
#####################################################################
# #
@@ -944,7 +944,7 @@ class Generator:
if not args.get('mode', None):
args['mode'] = self._mode
args['stringType'] = 'nestinglevel'
- nestingLevel = self._script.getNestingLevel(obj)
+ nestingLevel = self._script.utilities.nestingLevel(obj)
if nestingLevel:
result.append(self._script.formatting.getString(**args)\
% nestingLevel)
@@ -966,7 +966,8 @@ class Generator:
radioGroupLabel = relation.getTarget(0)
break
if radioGroupLabel:
- result.append(self._script.getDisplayedText(radioGroupLabel))
+ result.append(self._script.utilities.\
+ displayedText(radioGroupLabel))
else:
parent = obj.parent
while parent and (parent.parent != parent):
@@ -987,8 +988,8 @@ class Generator:
found. Otherwise, an empty array is returned.
"""
result = []
- text = self._script.getDisplayedText(
- self._script.getRealActiveDescendant(obj))
+ text = self._script.utilities.displayedText(
+ self._script.utilities.realActiveDescendant(obj))
if text:
result = [text]
return result
@@ -999,7 +1000,7 @@ class Generator:
array of strings for use by speech and braille that represents
the role of the object actually being painted in the cell.
"""
- rad = self._script.getRealActiveDescendant(obj)
+ rad = self._script.utilities.realActiveDescendant(obj)
args['role'] = rad.getRole()
return self._generateRoleName(rad, **args)
diff --git a/src/orca/gnomespeechfactory.py b/src/orca/gnomespeechfactory.py
index 54fe641..91a85d4 100644
--- a/src/orca/gnomespeechfactory.py
+++ b/src/orca/gnomespeechfactory.py
@@ -750,7 +750,8 @@ class SpeechServer(speechserver.SpeechServer):
"""
chname = chnames.getCharacterName(character)
if orca_state.activeScript and orca_state.usePronunciationDictionary:
- chname = orca_state.activeScript.adjustForPronunciation(chname)
+ chname = orca_state.activeScript.\
+ utilities.adjustForPronunciation(chname)
self.speak(chname, acss)
def speakUtterances(self, utterances, acss=None, interrupt=True):
diff --git a/src/orca/liveregions.py b/src/orca/liveregions.py
index c56f8d6..ab2f1f6 100644
--- a/src/orca/liveregions.py
+++ b/src/orca/liveregions.py
@@ -304,7 +304,7 @@ class LiveRegionManager:
"""User toggle to set all live regions to LIVE_OFF or back to their
original politeness."""
# start at the document frame
- docframe = self._script.getDocumentFrame()
+ docframe = self._script.utilities.documentFrame()
# get the URI of the page. It is used as a partial key.
uri = self._script.bookmarks.getURIKey()
@@ -406,13 +406,16 @@ class LiveRegionManager:
try:
if attrs['container-atomic'] == 'true':
# expand the source if atomic is true
- newcontent = self._script.expandEOCs(event.source)
+ newcontent = \
+ self._script.utilities.expandEOCs(event.source)
else:
# expand the target if atomic is false
- newcontent = self._script.expandEOCs(event.any_data)
+ newcontent = \
+ self._script.utilities.expandEOCs(event.any_data)
except (KeyError, TypeError):
# expand the target if there is no ARIA markup
- newcontent = self._script.expandEOCs(event.any_data)
+ newcontent = \
+ self._script.utilities.expandEOCs(event.any_data)
# add our content to the returned message or return None if no
# content
@@ -478,7 +481,7 @@ class LiveRegionManager:
def _getLabelsAsUtterances(self, obj):
"""Get the labels for a given object"""
# try the Gecko label getter first
- uttstring = self._script.getDisplayedLabel(obj)
+ uttstring = self._script.utilities.displayedLabel(obj)
if uttstring:
return [uttstring.strip()]
# often we see a table cell. I'll implement my own label getter
@@ -493,11 +496,11 @@ class LiveRegionManager:
# Note: getRowHeader() fails for most markup. We will use the
# relation when the markup is good (when getRowHeader() works)
# so we won't see this code in those cases.
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
row = itable.getRowAtIndex(index)
header = itable.getAccessibleAt(row, 0)
# expand the header
- return [self._script.expandEOCs(header).strip()]
+ return [self._script.utilities.expandEOCs(header).strip()]
except NotImplementedError:
pass
@@ -505,7 +508,7 @@ class LiveRegionManager:
# element.
parentattrs = self._getAttrDictionary(obj.parent)
if 'tag' in parentattrs and parentattrs['tag'] == 'TR':
- return [self._script.expandEOCs( \
+ return [self._script.utilities.expandEOCs( \
obj.parent.getChildAtIndex(0)).strip()]
# Sorry, no valid labels found
@@ -578,7 +581,7 @@ class LiveRegionManager:
def _getPath(self, obj):
""" Returns, as a tuple of integers, the path from the given object
to the document frame."""
- docframe = self._script.getDocumentFrame()
+ docframe = self._script.utilities.documentFrame()
path = []
while 1:
if obj.parent is None or obj == docframe:
diff --git a/src/orca/mouse_review.py b/src/orca/mouse_review.py
index fd98062..29eed6c 100644
--- a/src/orca/mouse_review.py
+++ b/src/orca/mouse_review.py
@@ -134,7 +134,7 @@ class _ItemContext:
"""
if not self.script or not self.script.speakWordUnderMouse(self.acc):
return None
- word, start, end = self.script.getWordAtCoords(self.acc, x, y)
+ word, start, end = self.script.utilities.wordAtCoords(self.acc, x, y)
return _WordContext(word, self.acc, start, end)
class MouseReviewer:
@@ -305,7 +305,7 @@ class MouseReviewer:
for frame in app:
if not frame:
continue
- acc = script.getComponentAtDesktopCoords(frame, x, y)
+ acc = script.utilities.componentAtDesktopCoords(frame, x, y)
if acc:
try:
z_order = self._getZOrder(frame.name)
diff --git a/src/orca/orca.py b/src/orca/orca.py
index d5b1080..77894cd 100644
--- a/src/orca/orca.py
+++ b/src/orca/orca.py
@@ -665,7 +665,7 @@ def keyEcho(event):
eventType == KeyEventType.PRINTABLE \
and not _orcaModifierPressed \
and orca_state.activeScript \
- and orca_state.activeScript.willEchoCharacter(event)
+ and orca_state.activeScript.utilities.willEchoCharacter(event)
# One last check for echoing -- a PRINTABLE key may have squeaked
# through due to the enableEchoByCharacter check above. We only
diff --git a/src/orca/orca_gui_prefs.py b/src/orca/orca_gui_prefs.py
index 7efa316..6706665 100644
--- a/src/orca/orca_gui_prefs.py
+++ b/src/orca/orca_gui_prefs.py
@@ -952,9 +952,9 @@ class OrcaSetupGUI(orca_gtkbuilder.GtkBuilderWrapper):
defScript = default.Script(None)
[attrList, attrDict] = \
- defScript.textAttrsToDictionary(setAttributes)
+ defScript.utilities.stringToKeysAndDict(setAttributes)
[allAttrList, allAttrDict] = \
- defScript.textAttrsToDictionary(settings.allTextAttributes)
+ defScript.utilities.stringToKeysAndDict(settings.allTextAttributes)
for i in range(0, len(attrList)):
for path in range(0, len(allAttrList)):
@@ -992,9 +992,9 @@ class OrcaSetupGUI(orca_gtkbuilder.GtkBuilderWrapper):
defScript = default.Script(None)
[attrList, attrDict] = \
- defScript.textAttrsToDictionary(setAttributes)
+ defScript.utilities.stringToKeysAndDict(setAttributes)
[allAttrList, allAttrDict] = \
- defScript.textAttrsToDictionary(settings.allTextAttributes)
+ defScript.utilities.stringToKeysAndDict(settings.allTextAttributes)
for i in range(0, len(attrList)):
for path in range(0, len(allAttrList)):
@@ -1146,8 +1146,8 @@ class OrcaSetupGUI(orca_gtkbuilder.GtkBuilderWrapper):
# the known text attributes.
#
defScript = default.Script(None)
- [allAttrList, allAttrDict] = \
- defScript.textAttrsToDictionary(settings.allTextAttributes)
+ [allAttrList, allAttrDict] = defScript.utilities.stringToKeysAndDict(
+ settings.allTextAttributes)
for i in range(0, len(allAttrList)):
thisIter = model.append()
localizedKey = \
diff --git a/src/orca/script.py b/src/orca/script.py
index f6595a3..d31b651 100644
--- a/src/orca/script.py
+++ b/src/orca/script.py
@@ -47,6 +47,7 @@ import flat_review
import formatting
import keybindings
import orca_state
+import script_utilities
import settings
import speech_generator
import structural_navigation
@@ -81,6 +82,7 @@ class Script:
#
self.presentIfInactive = True
+ self.utilities = self.getUtilities()
self.structuralNavigation = self.getStructuralNavigation()
self.chat = self.getChat()
self.inputEventHandlers = {}
@@ -212,6 +214,11 @@ class Script:
"""
return None
+ def getUtilities(self):
+ """Returns the utilites for this script.
+ """
+ return script_utilities.Utilities(self)
+
def getEnabledStructuralNavigationTypes(self):
"""Returns a list of the structural navigation object types
enabled in this script.
diff --git a/src/orca/script_utilities.py b/src/orca/script_utilities.py
new file mode 100644
index 0000000..8c8fb8d
--- /dev/null
+++ b/src/orca/script_utilities.py
@@ -0,0 +1,2728 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Commonly-required utility methods needed by -- and potentially
+ customized by -- application and toolkit scripts. They have
+ been pulled out from the scripts because certain scripts had
+ gotten way too large as a result of including these methods."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__ = "LGPL"
+
+import math
+import pyatspi
+import re
+
+import debug
+import mouse_review
+import orca_state
+import settings
+
+from orca_i18n import _ # for gettext support
+from orca_i18n import ngettext # for ngettext support
+
+#############################################################################
+# #
+# Utilities #
+# #
+#############################################################################
+
+class Utilities:
+
+ EMBEDDED_OBJECT_CHARACTER = u'\ufffc'
+ WORDS_RE = re.compile("(\W+)", re.UNICODE)
+
+ # generatorCache
+ #
+ DISPLAYED_LABEL = 'displayedLabel'
+ DISPLAYED_TEXT = 'displayedText'
+ KEY_BINDING = 'keyBinding'
+ NESTING_LEVEL = 'nestingLevel'
+ NODE_LEVEL = 'nodeLevel'
+ REAL_ACTIVE_DESCENDANT = 'realActiveDescendant'
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ self._script = script
+
+ #########################################################################
+ # #
+ # Utilities for finding, identifying, and comparing accessibles #
+ # #
+ #########################################################################
+
+ @staticmethod
+ def __hasLabelForRelation(label):
+ """Check if label has a LABEL_FOR relation
+
+ Arguments:
+ - label: the label in question
+
+ Returns TRUE if label has a LABEL_FOR relation.
+ """
+ if (not label) or (label.getRole() != pyatspi.ROLE_LABEL):
+ return False
+
+ relations = label.getRelationSet()
+
+ for relation in relations:
+ if relation.getRelationType() == pyatspi.RELATION_LABEL_FOR:
+ return True
+
+ return False
+
+ @staticmethod
+ def __isLabeling(label, obj):
+ """Check if label is connected via LABEL_FOR relation with object
+
+ Arguments:
+ - obj: the object in question
+ - labeled: the label in question
+
+ Returns TRUE if label has a relation LABEL_FOR for object.
+ """
+
+ if not obj or not label or label.getRole() != pyatspi.ROLE_LABEL:
+ return False
+
+ relations = label.getRelationSet()
+ if not relations:
+ return False
+
+ for relation in relations:
+ if relation.getRelationType() == pyatspi.RELATION_LABEL_FOR:
+ for i in range(0, relation.getNTargets()):
+ target = relation.getTarget(i)
+ if target == obj:
+ return True
+
+ return False
+
+ @staticmethod
+ def activeWindow():
+ """Traverses the list of known apps looking for one who has an
+ immediate child (i.e., a window) whose state includes the active
+ state.
+
+ Returns the Python Accessible of the window that's active or None if
+ no windows are active.
+ """
+
+ apps = Utilities.knownApplications()
+ for app in apps:
+ for child in app:
+ try:
+ if child.getState().contains(pyatspi.STATE_ACTIVE):
+ return child
+ except:
+ debug.printException(debug.LEVEL_FINEST)
+
+ return None
+
+ @staticmethod
+ def allDescendants(root, onlyShowing=True):
+ """Returns a list of all objects under the given root. Objects
+ are returned in no particular order - this function does a simple
+ tree traversal, ignoring the children of objects which report the
+ MANAGES_DESCENDANTS state.
+
+ Arguments:
+ - root: the Accessible object to traverse
+ - onlyShowing: examine only those objects that are SHOWING
+
+ Returns: a list of all objects under the specified object
+ """
+
+ if root.childCount <= 0:
+ return []
+
+ objlist = []
+ for i, child in enumerate(root):
+ debug.println(debug.LEVEL_FINEST,
+ "Script.allDescendants looking at child %d" % i)
+ if child \
+ and ((not onlyShowing) or (onlyShowing and \
+ (child.getState().contains(pyatspi.STATE_SHOWING)))):
+ objlist.append(child)
+ if (child.getState().contains( \
+ pyatspi.STATE_MANAGES_DESCENDANTS) == 0) \
+ and (child.childCount > 0):
+ objlist.extend(Utilities.allDescendants(child, onlyShowing))
+
+ return objlist
+
+ @staticmethod
+ def ancestorWithRole(obj, ancestorRoles, stopRoles):
+ """Returns the object of the specified roles which contains the
+ given object, or None if the given object is not contained within
+ an object the specified roles.
+
+ Arguments:
+ - obj: the Accessible object
+ - ancestorRoles: the list of roles to look for
+ - stopRoles: the list of roles to stop the search at
+ """
+
+ if not obj:
+ return None
+
+ if not isinstance(ancestorRoles, [].__class__):
+ ancestorRoles = [ancestorRoles]
+
+ if not isinstance(stopRoles, [].__class__):
+ stopRoles = [stopRoles]
+
+ ancestor = None
+
+ obj = obj.parent
+ while obj and (obj != obj.parent):
+ if obj.getRole() in ancestorRoles:
+ ancestor = obj
+ break
+ elif obj.getRole() in stopRoles:
+ break
+ else:
+ obj = obj.parent
+
+ return ancestor
+
+ def cellIndex(self, obj):
+ """Returns the index of the cell which should be used with the
+ table interface. This is necessary because in some apps we
+ cannot count on getIndexInParent() returning the index we need.
+
+ Arguments:
+ -obj: the table cell whose index we need.
+ """
+
+ return obj.getIndexInParent()
+
+ def childNodes(self, obj):
+ """Gets all of the children that have RELATION_NODE_CHILD_OF pointing
+ to this expanded table cell.
+
+ Arguments:
+ -obj: the Accessible Object
+
+ Returns: a list of all the child nodes
+ """
+
+ try:
+ table = obj.parent.queryTable()
+ except:
+ return []
+ else:
+ if not obj.getState().contains(pyatspi.STATE_EXPANDED):
+ return []
+
+ nodes = []
+
+ # First see if this accessible implements RELATION_NODE_PARENT_OF.
+ # If it does, the full target list are the nodes. If it doesn't
+ # we'll do an old-school, row-by-row search for child nodes.
+ #
+ relations = obj.getRelationSet()
+ try:
+ for relation in relations:
+ if relation.getRelationType() == \
+ pyatspi.RELATION_NODE_PARENT_OF:
+ for target in range(relation.getNTargets()):
+ nodes.append(relation.getTarget(target))
+ return nodes
+ except:
+ pass
+
+ index = self.cellIndex(obj)
+ row = table.getRowAtIndex(index)
+ col = table.getColumnAtIndex(index)
+ nodeLevel = self.nodeLevel(obj)
+ done = False
+
+ # Candidates will be in the rows beneath the current row.
+ # Only check in the current column and stop checking as
+ # soon as the node level of a candidate is equal or less
+ # than our current level.
+ #
+ for i in range(row+1, table.nRows):
+ cell = table.getAccessibleAt(i, col)
+ relations = cell.getRelationSet()
+ for relation in relations:
+ if relation.getRelationType() \
+ == pyatspi.RELATION_NODE_CHILD_OF:
+ nodeOf = relation.getTarget(0)
+ if self.isSameObject(obj, nodeOf):
+ nodes.append(cell)
+ else:
+ currentLevel = self.nodeLevel(nodeOf)
+ if currentLevel <= nodeLevel:
+ done = True
+ break
+ if done:
+ break
+
+ return nodes
+
+ def commonAncestor(self, a, b):
+ """Finds the common ancestor between Accessible a and Accessible b.
+
+ Arguments:
+ - a: Accessible
+ - b: Accessible
+ """
+
+ debug.println(debug.LEVEL_FINEST,
+ "script_utilities.commonAncestor...")
+
+ if (not a) or (not b):
+ return None
+
+ if a == b:
+ return a
+
+ aParents = [a]
+ try:
+ parent = a.parent
+ while parent and (parent.parent != parent):
+ aParents.append(parent)
+ parent = parent.parent
+ aParents.reverse()
+ except:
+ debug.printException(debug.LEVEL_FINEST)
+
+ bParents = [b]
+ try:
+ parent = b.parent
+ while parent and (parent.parent != parent):
+ bParents.append(parent)
+ parent = parent.parent
+ bParents.reverse()
+ except:
+ debug.printException(debug.LEVEL_FINEST)
+
+ commonAncestor = None
+
+ maxSearch = min(len(aParents), len(bParents))
+ i = 0
+ while i < maxSearch:
+ if self.isSameObject(aParents[i], bParents[i]):
+ commonAncestor = aParents[i]
+ i += 1
+ else:
+ break
+
+ debug.println(debug.LEVEL_FINEST,
+ "...script_utilities.commonAncestor")
+
+ return commonAncestor
+
+ def componentAtDesktopCoords(self, parent, x, y):
+ """Get the descendant component at the given desktop coordinates.
+
+ Arguments:
+
+ - parent: The parent component we are searching below.
+ - x: X coordinate.
+ - y: Y coordinate.
+
+ Returns end-node that contains the given coordinates, or None.
+ """
+ acc = self.popupItemAtDesktopCoords(x, y)
+ if acc:
+ return acc
+
+ container = parent
+ while True:
+ if container.getRole() == pyatspi.ROLE_PAGE_TAB_LIST:
+ try:
+ si = container.querySelection()
+ container = si.getSelectedChild(0)[0]
+ except NotImplementedError:
+ pass
+ try:
+ ci = container.queryComponent()
+ except:
+ return None
+ else:
+ inner_container = container
+ container = ci.getAccessibleAtPoint(x, y, pyatspi.DESKTOP_COORDS)
+ if not container or container.queryComponent() == ci:
+ break
+ if inner_container == parent:
+ return None
+ else:
+ return inner_container
+
+ def defaultButton(self, obj):
+ """Returns the default button in the dialog which contains obj.
+
+ Arguments:
+ - obj: the top-level object (e.g. window, frame, dialog) for
+ which the status bar is sought.
+ """
+
+ # There are some objects which are not worth descending.
+ #
+ skipRoles = [pyatspi.ROLE_TREE,
+ pyatspi.ROLE_TREE_TABLE,
+ pyatspi.ROLE_TABLE]
+
+ if obj.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS) \
+ or obj.getRole() in skipRoles:
+ return
+
+ defaultButton = None
+ # The default button is likely near the bottom of the window.
+ #
+ for i in range(obj.childCount - 1, -1, -1):
+ if obj[i].getRole() == pyatspi.ROLE_PUSH_BUTTON \
+ and obj[i].getState().contains(pyatspi.STATE_IS_DEFAULT):
+ defaultButton = obj[i]
+ elif not obj[i].getRole() in skipRoles:
+ defaultButton = self.defaultButton(obj[i])
+
+ if defaultButton:
+ break
+
+ return defaultButton
+
+ @staticmethod
+ def descendantsWithRole(root, role, onlyShowing=True):
+ """Returns a list of all objects of a specific role beneath the
+ given root.
+
+ Arguments:
+ - root: the Accessible object to traverse
+ - role: the string describing the Accessible role of the object
+ - onlyShowing: examine only those objects that are SHOWING
+
+ Returns a list of descendants of the root that are of the given role.
+ """
+
+ allObjs = Utilities.allDescendants(root, onlyShowing)
+ return filter(lambda o: o.getRole() == role, allObjs)
+
+ def displayedLabel(self, obj):
+ """If there is an object labelling the given object, return the
+ text being displayed for the object labelling this object.
+ Otherwise, return None.
+
+ Argument:
+ - obj: the object in question
+
+ Returns the string of the object labelling this object, or None
+ if there is nothing of interest here.
+ """
+
+ try:
+ return self._script.generatorCache[self.DISPLAYED_LABEL][obj]
+ except:
+ if not self._script.generatorCache.has_key(self.DISPLAYED_LABEL):
+ self._script.generatorCache[self.DISPLAYED_LABEL] = {}
+ labelString = None
+
+ labels = self.labelsForObject(obj)
+ for label in labels:
+ labelString = \
+ self.appendString(labelString, self.displayedText(label))
+
+ self._script.generatorCache[self.DISPLAYED_LABEL][obj] = labelString
+ return self._script.generatorCache[self.DISPLAYED_LABEL][obj]
+
+ def _displayedTextInComboBox(self, combo):
+
+ """Returns the text being displayed in a combo box. If nothing is
+ displayed, then None is returned.
+
+ Arguments:
+ - combo: the combo box
+
+ Returns the text in the combo box or an empty string if nothing is
+ displayed.
+ """
+
+ displayedText = None
+
+ # Find the text displayed in the combo box. This is either:
+ #
+ # 1) The last text object that's a child of the combo box
+ # 2) The selected child of the combo box.
+ # 3) The contents of the text of the combo box itself when
+ # treated as a text object.
+ #
+ # Preference is given to #1, if it exists.
+ #
+ # If the label of the combo box is the same as the utterance for
+ # the child object, then this utterance is only displayed once.
+ #
+ # [[[TODO: WDW - Combo boxes are complex beasts. This algorithm
+ # needs serious work. Logged as bugzilla bug 319745.]]]
+ #
+ textObj = None
+ for child in combo:
+ if child and child.getRole() == pyatspi.ROLE_TEXT:
+ textObj = child
+
+ if textObj:
+ [displayedText, caretOffset, startOffset] = \
+ self._script.getTextLineAtCaret(textObj)
+ #print "TEXTOBJ", displayedText
+ else:
+ try:
+ comboSelection = combo.querySelection()
+ selectedItem = comboSelection.getSelectedChild(0)
+ except:
+ selectedItem = None
+
+ if selectedItem:
+ displayedText = self.displayedText(selectedItem)
+ #print "SELECTEDITEM", displayedText
+ elif combo.name and len(combo.name):
+ # We give preference to the name over the text because
+ # the text for combo boxes seems to never change in
+ # some cases. The main one where we see this is in
+ # the gaim "Join Chat" window.
+ #
+ displayedText = combo.name
+ #print "NAME", displayedText
+ else:
+ [displayedText, caretOffset, startOffset] = \
+ self._script.getTextLineAtCaret(combo)
+ # Set to None instead of empty string.
+ displayedText = displayedText or None
+ #print "TEXT", displayedText
+
+ return displayedText
+
+ def displayedText(self, obj):
+ """Returns the text being displayed for an object.
+
+ Arguments:
+ - obj: the object
+
+ Returns the text being displayed for an object or None if there isn't
+ any text being shown.
+ """
+
+ try:
+ return self._script.generatorCache[self.DISPLAYED_TEXT][obj]
+ except:
+ if not self._script.generatorCache.has_key(self.DISPLAYED_TEXT):
+ self._script.generatorCache[self.DISPLAYED_TEXT] = {}
+ displayedText = None
+
+ role = obj.getRole()
+ if role == pyatspi.ROLE_COMBO_BOX:
+ displayedText = self._displayedTextInComboBox(obj)
+ self._script.generatorCache[self.DISPLAYED_TEXT][obj] = \
+ displayedText
+ return self._script.generatorCache[self.DISPLAYED_TEXT][obj]
+
+ # The accessible text of an object is used to represent what is
+ # drawn on the screen.
+ #
+ try:
+ text = obj.queryText()
+ except NotImplementedError:
+ pass
+ else:
+ displayedText = text.getText(0, self._script.getTextEndOffset(text))
+
+ # [[[WDW - HACK to account for things such as Gecko that want
+ # to use the EMBEDDED_OBJECT_CHARACTER on a label to hold the
+ # object that has the real accessible text for the label. We
+ # detect this by the specfic case where the text for the
+ # current object is a single EMBEDDED_OBJECT_CHARACTER. In
+ # this case, we look to the child for the real text.]]]
+ #
+ unicodeText = displayedText.decode("UTF-8")
+ if unicodeText \
+ and len(unicodeText) == 1 \
+ and unicodeText[0] == self.EMBEDDED_OBJECT_CHARACTER \
+ and obj.childCount > 0:
+ try:
+ displayedText = self.displayedText(obj[0])
+ except:
+ debug.printException(debug.LEVEL_WARNING)
+ elif unicodeText:
+ # [[[TODO: HACK - Welll.....we'll just plain ignore any
+ # text with EMBEDDED_OBJECT_CHARACTERs here. We still need a
+ # general case to handle this stuff and expand objects
+ # with EMBEDDED_OBJECT_CHARACTERs.]]]
+ #
+ for i in xrange(len(unicodeText)):
+ if unicodeText[i] == self.EMBEDDED_OBJECT_CHARACTER:
+ displayedText = None
+ break
+
+ if not displayedText:
+ displayedText = obj.name
+
+ # [[[WDW - HACK because push buttons can have labels as their
+ # children. An example of this is the Font: button on the General
+ # tab in the Editing Profile dialog in gnome-terminal.
+ #
+ if not displayedText and role == pyatspi.ROLE_PUSH_BUTTON:
+ for child in obj:
+ if child.getRole() == pyatspi.ROLE_LABEL:
+ childText = self.displayedText(child)
+ if childText and len(childText):
+ displayedText = \
+ self.appendString(displayedText, childText)
+
+ self._script.generatorCache[self.DISPLAYED_TEXT][obj] = displayedText
+ return self._script.generatorCache[self.DISPLAYED_TEXT][obj]
+
+ def documentFrame(self):
+ """Returns the document frame which is displaying the content.
+ Note that this is intended primarily for web content."""
+
+ return None
+
+ def documentFrameURI(self):
+ """Returns the URI of the document frame that is active."""
+
+ return None
+
+ @staticmethod
+ def focusedObject(root):
+ """Returns the accessible that has focus under or including the
+ given root.
+
+ TODO: This will currently traverse all children, whether they are
+ visible or not and/or whether they are children of parents that
+ manage their descendants. At some point, this method should be
+ optimized to take such things into account.
+
+ Arguments:
+ - root: the root object where to start searching
+
+ Returns the object with the FOCUSED state or None if no object with
+ the FOCUSED state can be found.
+ """
+
+ if root.getState().contains(pyatspi.STATE_FOCUSED):
+ return root
+
+ for child in root:
+ try:
+ candidate = Utilities.focusedObject(child)
+ if candidate:
+ return candidate
+ except:
+ pass
+
+ return None
+
+ def frameAndDialog(self, obj):
+ """Returns the frame and (possibly) the dialog containing obj."""
+
+ results = [None, None]
+
+ parent = obj.parent
+ while parent and (parent.parent != parent):
+ if parent.getRole() == pyatspi.ROLE_FRAME:
+ results[0] = parent
+ if parent.getRole() in [pyatspi.ROLE_DIALOG,
+ pyatspi.ROLE_FILE_CHOOSER]:
+ results[1] = parent
+ parent = parent.parent
+
+ return results
+
+ def hasMatchingHierarchy(self, obj, rolesList):
+ """Called to determine if the given object and it's hierarchy of
+ parent objects, each have the desired roles. Please note: You
+ should strongly consider an alternative means for determining
+ that a given object is the desired item. Failing that, you should
+ include only enough of the hierarchy to make the determination.
+ If the developer of the application you are providing access to
+ does so much as add an Adjustment to reposition a widget, this
+ method can fail. You have been warned.
+
+ Arguments:
+ - obj: the accessible object to check.
+ - rolesList: the list of desired roles for the components and the
+ hierarchy of its parents.
+
+ Returns True if all roles match.
+ """
+
+ current = obj
+ for role in rolesList:
+ if current is None:
+ return False
+
+ if not isinstance(role, list):
+ role = [role]
+
+ if isinstance(role[0], str):
+ current_role = current.getRoleName()
+ else:
+ current_role = current.getRole()
+
+ if not current_role in role:
+ return False
+
+ current = self.validParent(current)
+
+ return True
+
+ def inFindToolbar(self, obj=None):
+ """Returns True if the given object is in the Find toolbar.
+
+ Arguments:
+ - obj: an accessible object
+ """
+
+ if not obj:
+ obj = orca_state.locusOfFocus
+
+ if obj and obj.getRole() == pyatspi.ROLE_ENTRY \
+ and obj.parent.getRole() == pyatspi.ROLE_TOOL_BAR:
+ return True
+
+ return False
+
+ def isFunctionalDialog(self, obj):
+ """Returns True if the window is a functioning as a dialog.
+ This method should be subclassed by application scripts as
+ needed.
+ """
+
+ return False
+
+ def isLayoutOnly(self, obj):
+ """Returns True if the given object is a table and is for layout
+ purposes only."""
+
+ layoutOnly = False
+
+ if obj:
+ attributes = obj.getAttributes()
+ else:
+ attributes = None
+
+ if obj and (obj.getRole() == pyatspi.ROLE_TABLE) and attributes:
+ for attribute in attributes:
+ if attribute == "layout-guess:true":
+ layoutOnly = True
+ break
+ elif obj and (obj.getRole() == pyatspi.ROLE_PANEL):
+ text = self.displayedText(obj)
+ label = self.displayedLabel(obj)
+ if not ((label and len(label)) or (text and len(text))):
+ layoutOnly = True
+
+ if layoutOnly:
+ debug.println(debug.LEVEL_FINEST,
+ "Object deemed to be for layout purposes only: %s" \
+ % obj)
+
+ return layoutOnly
+
+ @staticmethod
+ def isInActiveApp(obj):
+ """Returns True if the given object is from the same application that
+ currently has keyboard focus.
+
+ Arguments:
+ - obj: an Accessible object
+ """
+
+ if not obj or not orca_state.locusOfFocus:
+ return False
+
+ return orca_state.locusOfFocus.getApplication() == obj.getApplication()
+
+ def isLink(self, obj):
+ """Returns True if obj is a link."""
+
+ return obj and obj.getRole() == pyatspi.ROLE_LINK
+
+ def isReadOnlyTextArea(self, obj):
+ """Returns True if obj is a text entry area that is read only."""
+
+ if not self.isTextArea(obj):
+ return False
+
+ state = obj.getState()
+ readOnly = state.contains(pyatspi.STATE_FOCUSABLE) \
+ and not state.contains(pyatspi.STATE_EDITABLE)
+ debug.println(debug.LEVEL_ALL,
+ "isReadOnlyTextArea=%s for %s" \
+ % (readOnly, debug.getAccessibleDetails(obj)))
+
+ return readOnly
+
+ def isSameObject(self, obj1, obj2):
+ if (obj1 == obj2):
+ return True
+ elif (not obj1) or (not obj2):
+ return False
+
+ try:
+ if (obj1.name != obj2.name) or (obj1.getRole() != obj2.getRole()):
+ return False
+ else:
+ # Gecko sometimes creates multiple accessibles to represent
+ # the same object. If the two objects have the same name
+ # and the same role, check the extents. If those also match
+ # then the two objects are for all intents and purposes the
+ # same object.
+ #
+ extents1 = \
+ obj1.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
+ extents2 = \
+ obj2.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
+ if (extents1.x == extents2.x) and \
+ (extents1.y == extents2.y) and \
+ (extents1.width == extents2.width) and \
+ (extents1.height == extents2.height):
+ return True
+
+ # When we're looking at children of objects that manage
+ # their descendants, we will often get different objects
+ # that point to the same logical child. We want to be able
+ # to determine if two objects are in fact pointing to the
+ # same child.
+ # If we cannot do so easily (i.e., object equivalence), we examine
+ # the hierarchy and the object index at each level.
+ #
+ parent1 = obj1
+ parent2 = obj2
+ while (parent1 and parent2 and \
+ parent1.getState().contains( \
+ pyatspi.STATE_TRANSIENT) and \
+ parent2.getState().contains(pyatspi.STATE_TRANSIENT)):
+ if parent1.getIndexInParent() != parent2.getIndexInParent():
+ return False
+ parent1 = parent1.parent
+ parent2 = parent2.parent
+ if parent1 and parent2 and parent1 == parent2:
+ return self.realActiveDescendant(obj1).name == \
+ self.realActiveDescendant(obj2).name
+ except:
+ pass
+
+ # [[[TODO - JD: Why is this here? If it is truly limited to the
+ # Java toolkit, it should be dealt with in Orca's Java toolkit
+ # script. If it applies more broadly we should update the comment
+ # to reflect that.]]]
+ #
+ # In java applications, TRANSIENT state is missing for tree items
+ # (fix for bug #352250)
+ #
+ try:
+ parent1 = obj1
+ parent2 = obj2
+ while parent1 and parent2 and \
+ parent1.getRole() == pyatspi.ROLE_LABEL and \
+ parent2.getRole() == pyatspi.ROLE_LABEL:
+ if parent1.getIndexInParent() != parent2.getIndexInParent():
+ return False
+ parent1 = parent1.parent
+ parent2 = parent2.parent
+ if parent1 and parent2 and parent1 == parent2:
+ return True
+ except:
+ pass
+
+ return False
+
+ def isTextArea(self, obj):
+ """Returns True if obj is a GUI component that is for entering text.
+
+ Arguments:
+ - obj: an accessible
+ """
+
+ if self.isLink(obj):
+ return False
+
+ return obj and obj.getRole() in (pyatspi.ROLE_TEXT,
+ pyatspi.ROLE_ENTRY,
+ pyatspi.ROLE_PARAGRAPH,
+ pyatspi.ROLE_TERMINAL)
+
+ @staticmethod
+ def knownApplications():
+ """Retrieves the list of currently running apps for the desktop
+ as a list of Accessible objects.
+ """
+
+ debug.println(debug.LEVEL_FINEST,
+ "knownApplications...")
+
+ apps = filter(lambda x: x is not None,
+ pyatspi.Registry.getDesktop(0))
+
+ debug.println(debug.LEVEL_FINEST,
+ "...knownApplications")
+
+ return apps
+
+ def labelsForObject(self, obj):
+ """Return a list of the objects that are labelling this object.
+
+ Argument:
+ - obj: the object in question
+
+ Returns a list of the objects that are labelling this object.
+ """
+
+ # For some reason, some objects are labelled by the same thing
+ # more than once. Go figure, but we need to check for this.
+ #
+ label = []
+ relations = obj.getRelationSet()
+ allTargets = []
+
+ for relation in relations:
+ if relation.getRelationType() \
+ == pyatspi.RELATION_LABELLED_BY:
+
+ # The object can be labelled by more than one thing, so we just
+ # get all the labels (from unique objects) and append them
+ # together. An example of such objects live in the "Basic"
+ # page of the gnome-accessibility-keyboard-properties app.
+ # The "Delay" and "Speed" objects are labelled both by
+ # their names and units.
+ #
+ for i in range(0, relation.getNTargets()):
+ target = relation.getTarget(i)
+ if not target in allTargets:
+ allTargets.append(target)
+ label.append(target)
+
+ # [[[TODO: HACK - we've discovered oddness in hierarchies such as
+ # the gedit Edit->Preferences dialog. In this dialog, we have
+ # labeled groupings of objects. The grouping is done via a FILLER
+ # with two children - one child is the overall label, and the
+ # other is the container for the grouped objects. When we detect
+ # this, we add the label to the overall context.
+ #
+ # We are also looking for objects which have a PANEL or a FILLER as
+ # parent, and its parent has more children. Through these children,
+ # a potential label with LABEL_FOR relation may exists. We want to
+ # present this label.
+ # This case can be seen in FileChooserDemo application, in Open dialog
+ # window, the line with "Look In" label, a combobox and some
+ # presentation buttons.
+ #
+ # Finally, we are searching the hierarchy of embedded components for
+ # children that are labels.]]]
+ #
+ if not len(label):
+ potentialLabels = []
+ useLabel = False
+ if (obj.getRole() == pyatspi.ROLE_EMBEDDED):
+ candidate = obj
+ while candidate.childCount:
+ candidate = candidate[0]
+ # The parent of this object may contain labels
+ # or it may contain filler that contains labels.
+ #
+ candidate = candidate.parent
+ for child in candidate:
+ if child.getRole() == pyatspi.ROLE_FILLER:
+ candidate = child
+ break
+ # If there are labels in this embedded component,
+ # they should be here.
+ #
+ for child in candidate:
+ if child.getRole() == pyatspi.ROLE_LABEL:
+ useLabel = True
+ potentialLabels.append(child)
+ elif ((obj.getRole() == pyatspi.ROLE_FILLER) \
+ or (obj.getRole() == pyatspi.ROLE_PANEL)) \
+ and (obj.childCount == 2):
+ child0, child1 = obj
+ child0_role = child0.getRole()
+ child1_role = child1.getRole()
+ if child0_role == pyatspi.ROLE_LABEL \
+ and not self.__hasLabelForRelation(child0) \
+ and child1_role in [pyatspi.ROLE_FILLER, \
+ pyatspi.ROLE_PANEL]:
+ useLabel = True
+ potentialLabels.append(child0)
+ elif child1_role == pyatspi.ROLE_LABEL \
+ and not self.__hasLabelForRelation(child1) \
+ and child0_role in [pyatspi.ROLE_FILLER, \
+ pyatspi.ROLE_PANEL]:
+ useLabel = True
+ potentialLabels.append(child1)
+ else:
+ parent = obj.parent
+ if parent and \
+ ((parent.getRole() == pyatspi.ROLE_FILLER) \
+ or (parent.getRole() == pyatspi.ROLE_PANEL)):
+ for potentialLabel in parent:
+ try:
+ useLabel = self.__isLabeling(potentialLabel, obj)
+ if useLabel:
+ potentialLabels.append(potentialLabel)
+ break
+ except:
+ pass
+
+ if useLabel and len(potentialLabels):
+ label = potentialLabels
+
+ return label
+
+ @staticmethod
+ def linkIndex(obj, characterIndex):
+ """A brute force method to see if an offset is a link. This
+ is provided because not all Accessible Hypertext implementations
+ properly support the getLinkIndex method. Returns an index of
+ 0 or greater of the characterIndex is on a hyperlink.
+
+ Arguments:
+ -obj: the object with the Accessible Hypertext specialization
+ -characterIndex: the text position to check
+ """
+
+ if not obj:
+ return -1
+
+ try:
+ obj.queryText()
+ except NotImplementedError:
+ return -1
+
+ try:
+ hypertext = obj.queryHypertext()
+ except NotImplementedError:
+ return -1
+
+ for i in xrange(hypertext.getNLinks()):
+ link = hypertext.getLink(i)
+ if (characterIndex >= link.startIndex) \
+ and (characterIndex <= link.endIndex):
+ return i
+
+ return -1
+
+ def nestingLevel(self, obj):
+ """Determines the nesting level of this object in a list. If this
+ object is not in a list relation, then 0 will be returned.
+
+ Arguments:
+ -obj: the Accessible object
+ """
+
+ if not obj:
+ return 0
+
+ try:
+ return self._script.generatorCache[self.NESTING_LEVEL][obj]
+ except:
+ if not self._script.generatorCache.has_key(self.NESTING_LEVEL):
+ self._script.generatorCache[self.NESTING_LEVEL] = {}
+
+ nestingLevel = 0
+ parent = obj.parent
+ while parent.parent.getRole() == pyatspi.ROLE_LIST:
+ nestingLevel += 1
+ parent = parent.parent
+
+ self._script.generatorCache[self.NESTING_LEVEL][obj] = nestingLevel
+ return self._script.generatorCache[self.NESTING_LEVEL][obj]
+
+ def nodeLevel(self, obj):
+ """Determines the node level of this object if it is in a tree
+ relation, with 0 being the top level node. If this object is
+ not in a tree relation, then -1 will be returned.
+
+ Arguments:
+ -obj: the Accessible object
+ """
+
+ if not obj:
+ return -1
+
+ try:
+ return self._script.generatorCache[self.NODE_LEVEL][obj]
+ except:
+ if not self._script.generatorCache.has_key(self.NODE_LEVEL):
+ self._script.generatorCache[self.NODE_LEVEL] = {}
+
+ nodes = []
+ node = obj
+ done = False
+ while not done:
+ relations = node.getRelationSet()
+ node = None
+ for relation in relations:
+ if relation.getRelationType() \
+ == pyatspi.RELATION_NODE_CHILD_OF:
+ node = relation.getTarget(0)
+ break
+
+ # We want to avoid situations where something gives us an
+ # infinite cycle of nodes. Bon Echo has been seen to do
+ # this (see bug 351847).
+ #
+ if (len(nodes) > 100) or nodes.count(node):
+ debug.println(debug.LEVEL_WARNING,
+ "nodeLevel detected a cycle!!!")
+ done = True
+ elif node:
+ nodes.append(node)
+ debug.println(debug.LEVEL_FINEST,
+ "nodeLevel %d" % len(nodes))
+ else:
+ done = True
+
+ self._script.generatorCache[self.NODE_LEVEL][obj] = len(nodes) - 1
+ return self._script.generatorCache[self.NODE_LEVEL][obj]
+
+ def popupItemAtDesktopCoords(self, x, y):
+ """Since pop-up items often don't contain nested components, we need
+ a way to efficiently determine if the cursor is over a menu item.
+
+ Arguments:
+ - x: X coordinate.
+ - y: Y coordinate.
+
+ Returns a menu item the mouse is over, or None.
+ """
+
+ suspect_children = []
+ if self._script.lastSelectedMenu:
+ try:
+ si = self._script.lastSelectedMenu.querySelection()
+ except NotImplementedError:
+ return None
+
+ if si.nSelectedChildren > 0:
+ suspect_children = [si.getSelectedChild(0)]
+ else:
+ suspect_children = self._script.lastSelectedMenu
+ for child in suspect_children:
+ try:
+ ci = child.queryComponent()
+ except NotImplementedError:
+ continue
+
+ if ci.contains(x, y, pyatspi.DESKTOP_COORDS) \
+ and ci.getLayer() == pyatspi.LAYER_POPUP:
+ return child
+
+ @staticmethod
+ def pursueForFlatReview(obj):
+ """Determines if we should look any further at the object
+ for flat review."""
+
+ try:
+ state = obj.getState()
+ except:
+ debug.printException(debug.LEVEL_WARNING)
+ return False
+ else:
+ return state.contains(pyatspi.STATE_SHOWING)
+
+ def realActiveDescendant(self, obj):
+ """Given an object that should be a child of an object that
+ manages its descendants, return the child that is the real
+ active descendant carrying useful information.
+
+ Arguments:
+ - obj: an object that should be a child of an object that
+ manages its descendants.
+ """
+
+ try:
+ return self._script.\
+ generatorCache[self.REAL_ACTIVE_DESCENDANT][obj]
+ except:
+ if not self._script.\
+ generatorCache.has_key(self.REAL_ACTIVE_DESCENDANT):
+ self._script.generatorCache[self.REAL_ACTIVE_DESCENDANT] = {}
+ activeDescendant = None
+
+ # If obj is a table cell and all of it's children are table cells
+ # (probably cell renderers), then return the first child which has
+ # a non zero length text string. If no such object is found, just
+ # fall through and use the default approach below. See bug #376791
+ # for more details.
+ #
+ if obj.getRole() == pyatspi.ROLE_TABLE_CELL and obj.childCount:
+ nonTableCellFound = False
+ for child in obj:
+ if child.getRole() != pyatspi.ROLE_TABLE_CELL:
+ nonTableCellFound = True
+ if not nonTableCellFound:
+ for child in obj:
+ try:
+ text = child.queryText()
+ except NotImplementedError:
+ continue
+ else:
+ if text.getText(0, self._script.getTextEndOffset(text)):
+ activeDescendant = child
+
+ # [[[TODO: WDW - this is an odd hacky thing I've somewhat drawn
+ # from Gnopernicus. The notion here is that we get an active
+ # descendant changed event, but that object tends to have children
+ # itself and we need to decide what to do. Well...the idea here
+ # is that the last child (Gnopernicus chooses child(1)), tends to
+ # be the child with information. The previous children tend to
+ # be non-text or just there for spacing or something. You will
+ # see this in the various table demos of gtk-demo and you will
+ # also see this in the Contact Source Selector in Evolution.
+ #
+ # Just note that this is most likely not a really good solution
+ # for the general case. That needs more thought. But, this
+ # comment is here to remind us this is being done in poor taste
+ # and we need to eventually clean up our act.]]]
+ #
+ if not activeDescendant and obj and obj.childCount:
+ activeDescendant = obj[-1]
+
+ self._script.generatorCache[self.REAL_ACTIVE_DESCENDANT][obj] = \
+ activeDescendant or obj
+ return self._script.generatorCache[self.REAL_ACTIVE_DESCENDANT][obj]
+
+ def showingDescendants(self, parent):
+ """Given a parent that manages its descendants, return a list of
+ Accessible children that are actually showing. This algorithm
+ was inspired a little by the srw_elements_from_accessible logic
+ in Gnopernicus, and makes the assumption that the children of
+ an object that manages its descendants are arranged in a row
+ and column format.
+
+ Arguments:
+ - parent: The accessible which manages its descendants
+
+ Returns a list of Accessible descendants which are showing.
+ """
+
+ import sys
+
+ if not parent:
+ return []
+
+ if not parent.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS) \
+ or parent.childCount <= 50:
+ return []
+
+ try:
+ icomponent = parent.queryComponent()
+ except NotImplementedError:
+ return []
+
+ descendants = []
+
+ parentExtents = icomponent.getExtents(0)
+
+ # [[[TODO: WDW - HACK related to GAIL bug where table column
+ # headers seem to be ignored:
+ # http://bugzilla.gnome.org/show_bug.cgi?id=325809. The
+ # problem is that this causes getAccessibleAtPoint to return
+ # the cell effectively below the real cell at a given point,
+ # making a mess of everything. So...we just manually add in
+ # showing headers for now. The remainder of the logic below
+ # accidentally accounts for this offset, yet it should also
+ # work when bug 325809 is fixed.]]]
+ #
+ try:
+ table = parent.queryTable()
+ except NotImplementedError:
+ table = None
+
+ if table:
+ for i in range(0, table.nColumns):
+ header = table.getColumnHeader(i)
+ if header:
+ extents = header.queryComponent().getExtents(0)
+ stateset = header.getState()
+ if stateset.contains(pyatspi.STATE_SHOWING) \
+ and (extents.x >= 0) and (extents.y >= 0) \
+ and (extents.width > 0) and (extents.height > 0) \
+ and self.isVisibleRegion(
+ extents.x, extents.y,
+ extents.width, extents.height,
+ parentExtents.x, parentExtents.y,
+ parentExtents.width, parentExtents.height):
+ descendants.append(header)
+
+ # This algorithm goes left to right, top to bottom while attempting
+ # to do *some* optimization over queries. It could definitely be
+ # improved. The gridSize is a minimal chunk to jump around in the
+ # table.
+ #
+ gridSize = 7
+ currentY = parentExtents.y
+ while currentY < (parentExtents.y + parentExtents.height):
+ currentX = parentExtents.x
+ minHeight = sys.maxint
+ while currentX < (parentExtents.x + parentExtents.width):
+ child = \
+ icomponent.getAccessibleAtPoint(currentX, currentY + 1, 0)
+ if child:
+ extents = child.queryComponent().getExtents(0)
+ if extents.x >= 0 and extents.y >= 0:
+ newX = extents.x + extents.width
+ minHeight = min(minHeight, extents.height)
+ if not descendants.count(child):
+ descendants.append(child)
+ else:
+ newX = currentX + gridSize
+ else:
+ newX = currentX + gridSize
+ if newX <= currentX:
+ currentX += gridSize
+ else:
+ currentX = newX
+ if minHeight == sys.maxint:
+ minHeight = gridSize
+ currentY += minHeight
+
+ return descendants
+
+ def statusBar(self, obj):
+ """Returns the status bar in the window which contains obj.
+
+ Arguments:
+ - obj: the top-level object (e.g. window, frame, dialog) for which
+ the status bar is sought.
+ """
+
+ # There are some objects which are not worth descending.
+ #
+ skipRoles = [pyatspi.ROLE_TREE,
+ pyatspi.ROLE_TREE_TABLE,
+ pyatspi.ROLE_TABLE]
+
+ if obj.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS) \
+ or obj.getRole() in skipRoles:
+ return
+
+ statusBar = None
+ # The status bar is likely near the bottom of the window.
+ #
+ for i in range(obj.childCount - 1, -1, -1):
+ if obj[i].getRole() == pyatspi.ROLE_STATUS_BAR:
+ statusBar = obj[i]
+ elif not obj[i] in skipRoles:
+ statusBar = self.statusBar(obj[i])
+
+ if statusBar:
+ break
+
+ return statusBar
+
+ @staticmethod
+ def topLevelObject(obj):
+ """Returns the top-level object (frame, dialog ...) containing obj,
+ or None if obj is not inside a top-level object.
+
+ Arguments:
+ - obj: the Accessible object
+ """
+
+ debug.println(debug.LEVEL_FINEST,
+ "Finding top-level object for source.name="
+ + obj.name or "None")
+
+ while obj and obj.parent and (obj != obj.parent) \
+ and (obj.parent.getRole() != pyatspi.ROLE_APPLICATION):
+ obj = obj.parent
+ debug.println(debug.LEVEL_FINEST, "--> obj.name="
+ + obj.name or "None")
+
+ if obj and obj.parent and \
+ (obj.parent.getRole() == pyatspi.ROLE_APPLICATION):
+ pass
+ else:
+ obj = None
+
+ return obj
+
+ def unrelatedLabels(self, root):
+ """Returns a list containing all the unrelated (i.e., have no
+ relations to anything and are not a fundamental element of a
+ more atomic component like a combo box) labels under the given
+ root. Note that the labels must also be showing on the display.
+
+ Arguments:
+ - root the Accessible object to traverse
+
+ Returns a list of unrelated labels under the given root.
+ """
+
+ allLabels = self.descendantsWithRole(root, pyatspi.ROLE_LABEL)
+ unrelatedLabels = []
+ for label in allLabels:
+ relations = label.getRelationSet()
+ if len(relations) == 0:
+ parent = label.parent
+ if parent and (parent.getRole() == pyatspi.ROLE_PUSH_BUTTON):
+ pass
+ elif parent and (parent.getRole() == pyatspi.ROLE_PANEL) \
+ and (parent.name == label.name):
+ pass
+ elif label.getState().contains(pyatspi.STATE_SHOWING):
+ unrelatedLabels.append(label)
+
+ # Now sort the labels based on their geographic position, top to
+ # bottom, left to right. This is a very inefficient sort, but the
+ # assumption here is that there will not be a lot of labels to
+ # worry about.
+ #
+ sortedLabels = []
+ for label in unrelatedLabels:
+ label_extents = \
+ label.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
+ index = 0
+ for sortedLabel in sortedLabels:
+ sorted_extents = \
+ sortedLabel.queryComponent().getExtents(
+ pyatspi.DESKTOP_COORDS)
+ if (label_extents.y > sorted_extents.y) \
+ or ((label_extents.y == sorted_extents.y) \
+ and (label_extents.x > sorted_extents.x)):
+ index += 1
+ else:
+ break
+ sortedLabels.insert(index, label)
+
+ return sortedLabels
+
+ def unfocusedAlertAndDialogCount(self, obj):
+ """If the current application has one or more alert or dialog
+ windows and the currently focused window is not an alert or a dialog,
+ return a count of the number of alert and dialog windows, otherwise
+ return a count of zero.
+
+ Arguments:
+ - obj: the Accessible object
+
+ Returns the alert and dialog count.
+ """
+
+ alertAndDialogCount = 0
+ app = obj.getApplication()
+ window = Utilities.topLevelObject(obj)
+ if window and window.getRole() != pyatspi.ROLE_ALERT and \
+ window.getRole() != pyatspi.ROLE_DIALOG and \
+ not self.isFunctionalDialog(window):
+ for child in app:
+ if child.getRole() == pyatspi.ROLE_ALERT or \
+ child.getRole() == pyatspi.ROLE_DIALOG or \
+ self.isFunctionalDialog(child):
+ alertAndDialogCount += 1
+
+ return alertAndDialogCount
+
+ def uri(self, obj):
+ """Return the URI for a given link object.
+
+ Arguments:
+ - obj: the Accessible object.
+ """
+
+ try:
+ return obj.queryHyperlink().getURI(0)
+ except:
+ return None
+
+ def validParent(self, obj):
+ """Returns the first valid parent/ancestor of obj. We need to do
+ this in some applications and toolkits due to bogus hierarchies.
+
+ Arguments:
+ - obj: the Accessible object
+ """
+
+ if not obj:
+ return None
+
+ return obj.parent
+
+ #########################################################################
+ # #
+ # Utilities for working with the accessible text interface #
+ # #
+ #########################################################################
+
+ @staticmethod
+ def adjustTextSelection(obj, offset):
+ """Adjusts the end point of a text selection
+
+ Arguments:
+ - obj: the Accessible object.
+ - offset: the new end point - can be to the left or to the right
+ depending on the direction of selection
+ """
+
+ try:
+ text = obj.queryText()
+ except:
+ return
+
+ if not text.getNSelections():
+ caretOffset = text.caretOffset
+ startOffset = min(offset, caretOffset)
+ endOffset = max(offset, caretOffset)
+ text.addSelection(startOffset, endOffset)
+ else:
+ startOffset, endOffset = text.getSelection(0)
+ if offset < startOffset:
+ startOffset = offset
+ else:
+ endOffset = offset
+ text.setSelection(0, startOffset, endOffset)
+
+ def allSelectedText(self, obj):
+ """Get all the text applicable text selections for the given object.
+ including any previous or next text objects that also have
+ selected text and add in their text contents.
+
+ Arguments:
+ - obj: the text object to start extracting the selected text from.
+
+ Returns: all the selected text contents plus the start and end
+ offsets within the text for the given object.
+ """
+
+ textContents = ""
+ startOffset = 0
+ endOffset = 0
+ text = obj.queryText()
+ if text.getNSelections() > 0:
+ [textContents, startOffset, endOffset] = self.selectedText(obj)
+
+ current = obj
+ morePossibleSelections = True
+ while morePossibleSelections:
+ morePossibleSelections = False
+ for relation in current.getRelationSet():
+ if relation.getRelationType() == pyatspi.RELATION_FLOWS_FROM:
+ prevObj = relation.getTarget(0)
+ prevObjText = prevObj.queryText()
+ if prevObjText.getNSelections() > 0:
+ [newTextContents, start, end] = \
+ self.selectedText(prevObj)
+ textContents = newTextContents + " " + textContents
+ current = prevObj
+ morePossibleSelections = True
+ else:
+ displayedText = prevObjText.getText(0,
+ self._script.getTextEndOffset(prevObjText))
+ if len(displayedText) == 0:
+ current = prevObj
+ morePossibleSelections = True
+ break
+
+ current = obj
+ morePossibleSelections = True
+ while morePossibleSelections:
+ morePossibleSelections = False
+ for relation in current.getRelationSet():
+ if relation.getRelationType() == pyatspi.RELATION_FLOWS_TO:
+ nextObj = relation.getTarget(0)
+ nextObjText = nextObj.queryText()
+ if nextObjText.getNSelections() > 0:
+ [newTextContents, start, end] = \
+ self.selectedText(nextObj)
+ textContents += " " + newTextContents
+ current = nextObj
+ morePossibleSelections = True
+ else:
+ displayedText = nextObjText.getText(0,
+ self._script.getTextEndOffset(nextObjText))
+ if len(displayedText) == 0:
+ current = nextObj
+ morePossibleSelections = True
+ break
+
+ return [textContents, startOffset, endOffset]
+
+ @staticmethod
+ def allTextSelections(obj):
+ """Get a list of text selections in the given accessible object,
+ equivalent to getNSelections()*texti.getSelection()
+
+ Arguments:
+ - obj: An accessible.
+
+ Returns list of start and end offsets for multiple selections, or an
+ empty list if nothing is selected or if the accessible does not support
+ the text interface.
+ """
+
+ try:
+ text = obj.queryText()
+ except:
+ return []
+
+ rv = []
+ for i in xrange(text.getNSelections()):
+ rv.append(text.getSelection(i))
+
+ return rv
+
+ @staticmethod
+ def characterOffsetInParent(obj):
+ """Returns the character offset of the embedded object
+ character for this object in its parent's accessible text.
+
+ Arguments:
+ - obj: an Accessible that should implement the accessible
+ hyperlink specialization.
+
+ Returns an integer representing the character offset of the
+ embedded object character for this hyperlink in its parent's
+ accessible text, or -1 something was amuck.
+ """
+
+ try:
+ hyperlink = obj.queryHyperlink()
+ except NotImplementedError:
+ offset = -1
+ else:
+ # We need to make sure that this is an embedded object in
+ # some accessible text (as opposed to an imagemap link).
+ #
+ try:
+ obj.parent.queryText()
+ except NotImplementedError:
+ offset = -1
+ else:
+ offset = hyperlink.startIndex
+
+ return offset
+
+ @staticmethod
+ def clearTextSelection(obj):
+ """Clears the text selection if the object supports it.
+
+ Arguments:
+ - obj: the Accessible object.
+ """
+
+ try:
+ text = obj.queryText()
+ except:
+ return
+
+ for i in xrange(text.getNSelections()):
+ text.removeSelection(0)
+
+ def expandEOCs(self, obj, startOffset=0, endOffset=-1):
+ """Expands the current object replacing EMBEDDED_OBJECT_CHARACTERS
+ with their text.
+
+ Arguments
+ - obj: the object whose text should be expanded
+ - startOffset: the offset of the first character to be included
+ - endOffset: the offset of the last character to be included
+
+ Returns the fully expanded text for the object.
+ """
+
+ string = self.substring(obj, startOffset, endOffset)
+ if string:
+ unicodeText = string.decode("UTF-8")
+ if unicodeText and self.EMBEDDED_OBJECT_CHARACTER in unicodeText:
+ # If we're not getting the full text of this object, but
+ # rather a substring, we need to figure out the offset of
+ # the first child within this substring.
+ #
+ childOffset = 0
+ for child in obj:
+ if Utilities.characterOffsetInParent(child) >= startOffset:
+ break
+ childOffset += 1
+
+ toBuild = list(unicodeText)
+ count = toBuild.count(self.EMBEDDED_OBJECT_CHARACTER)
+ for i in xrange(count):
+ index = toBuild.index(self.EMBEDDED_OBJECT_CHARACTER)
+ child = obj[i + childOffset]
+ childText = self.expandEOCs(child)
+ if not childText:
+ childText = ""
+ toBuild[index] = childText.decode("UTF-8")
+ string = "".join(toBuild)
+
+ return string
+
+ def hasTextSelections(self, obj):
+ """Return an indication of whether this object has selected text.
+ Note that it's possible that this object has no selection, but is part
+ of a selected text area. Because of this, we need to check the
+ objects on either side to see if they are none zero length and
+ have text selections.
+
+ Arguments:
+ - obj: the text object to start checking for selected text.
+
+ Returns: an indication of whether this object has selected text,
+ or adjacent text objects have selected text.
+ """
+
+ currentSelected = False
+ otherSelected = False
+ text = obj.queryText()
+ nSelections = text.getNSelections()
+ if nSelections:
+ currentSelected = True
+ else:
+ otherSelected = False
+ text = obj.queryText()
+ displayedText = text.getText(0, self._script.getTextEndOffset(text))
+ if (text.caretOffset == 0) or len(displayedText) == 0:
+ current = obj
+ morePossibleSelections = True
+ while morePossibleSelections:
+ morePossibleSelections = False
+ for relation in current.getRelationSet():
+ if relation.getRelationType() == \
+ pyatspi.RELATION_FLOWS_FROM:
+ prevObj = relation.getTarget(0)
+ prevObjText = prevObj.queryText()
+ if prevObjText.getNSelections() > 0:
+ otherSelected = True
+ else:
+ displayedText = prevObjText.getText(0,
+ self._script.getTextEndOffset(prevObjText))
+ if len(displayedText) == 0:
+ current = prevObj
+ morePossibleSelections = True
+ break
+
+ current = obj
+ morePossibleSelections = True
+ while morePossibleSelections:
+ morePossibleSelections = False
+ for relation in current.getRelationSet():
+ if relation.getRelationType() == \
+ pyatspi.RELATION_FLOWS_TO:
+ nextObj = relation.getTarget(0)
+ nextObjText = nextObj.queryText()
+ if nextObjText.getNSelections() > 0:
+ otherSelected = True
+ else:
+ displayedText = nextObjText.getText(0,
+ self._script.getTextEndOffset(nextObjText))
+ if len(displayedText) == 0:
+ current = nextObj
+ morePossibleSelections = True
+ break
+
+ return [currentSelected, otherSelected]
+
+ @staticmethod
+ def isTextSelected(obj, startOffset, endOffset):
+ """Returns an indication of whether the text is selected by
+ comparing the text offset with the various selected regions of
+ text for this accessible object.
+
+ Arguments:
+ - obj: the Accessible object.
+ - startOffset: text start offset.
+ - endOffset: text end offset.
+
+ Returns an indication of whether the text is selected.
+ """
+
+ if startOffset == endOffset:
+ return False
+
+ try:
+ text = obj.queryText()
+ except:
+ return False
+
+ for i in xrange(text.getNSelections()):
+ [startSelOffset, endSelOffset] = text.getSelection(i)
+ if (startOffset >= startSelOffset) and (endOffset <= endSelOffset):
+ return True
+
+ return False
+
+ def isWordMisspelled(self, obj, offset):
+ """Identifies if the current word is flagged as misspelled by the
+ application. Different applications and toolkits flag misspelled
+ words differently. Thus each script will likely need to implement
+ its own version of this method.
+
+ Arguments:
+ - obj: An accessible which implements the accessible text interface.
+ - offset: Offset in the accessible's text for which to retrieve the
+ attributes.
+
+ Returns True if the word is flagged as misspelled.
+ """
+
+ return False
+
+ def offsetsForPhrase(self, obj):
+ """Return the start and end offset for the given phrase
+
+ Arguments:
+ - obj: the Accessible object
+ """
+
+ try:
+ text = obj.queryText()
+ except:
+ return [0, 0]
+
+ lastPos = self._script.pointOfReference.get("lastCursorPosition")
+ startOffset = lastPos[1]
+ endOffset = text.caretOffset
+
+ # Swap values if in wrong order.
+ #
+ if (startOffset > endOffset and endOffset != -1) or startOffset == -1:
+ temp = endOffset
+ endOffset = startOffset
+ startOffset = temp
+
+ return [startOffset, endOffset]
+
+ def offsetsForLine(self, obj):
+ """Return the start and end offset for the given line
+
+ Arguments:
+ - obj: the Accessible object
+ """
+
+ lineAndOffsets = self._script.getTextLineAtCaret(obj)
+ return [lineAndOffsets[1], lineAndOffsets[2]]
+
+ def offsetsForWord(self, obj):
+ """Return the start and end offset for the given word
+
+ Arguments:
+ - obj: the Accessible object
+ """
+
+ try:
+ text = obj.queryText()
+ except:
+ return [0, 0]
+
+ wordAndOffsets = text.getTextAtOffset(
+ text.caretOffset, pyatspi.TEXT_BOUNDARY_WORD_START)
+
+ return [wordAndOffsets[1], wordAndOffsets[2]]
+
+ def offsetsForChar(self, obj):
+ """Return the start and end offset for the given character
+
+ Arguments:
+ - obj: the Accessible object
+ """
+
+ try:
+ text = obj.queryText()
+ except:
+ return [0, 0]
+
+ if orca_state.lastInputEvent.modifiers & settings.SHIFT_MODIFIER_MASK \
+ and orca_state.lastNonModifierKeyEvent.event_string == "Right":
+ startOffset = text.caretOffset - 1
+ endOffset = text.caretOffset
+ else:
+ startOffset = text.caretOffset
+ endOffset = text.caretOffset + 1
+
+ return [startOffset, endOffset]
+
+ @staticmethod
+ def queryNonEmptyText(obj):
+ """Get the text interface associated with an object, if it is
+ non-empty.
+
+ Arguments:
+ - obj: an accessible object
+ """
+
+ try:
+ text = obj.queryText()
+ except:
+ pass
+ else:
+ if text.characterCount:
+ return text
+
+ return None
+
+ @staticmethod
+ def selectedText(obj):
+ """Get the text selection for the given object.
+
+ Arguments:
+ - obj: the text object to extract the selected text from.
+
+ Returns: the selected text contents plus the start and end
+ offsets within the text.
+ """
+
+ textContents = ""
+ textObj = obj.queryText()
+ nSelections = textObj.getNSelections()
+ for i in range(0, nSelections):
+ [startOffset, endOffset] = textObj.getSelection(i)
+
+ debug.println(debug.LEVEL_FINEST,
+ "getSelectedText: selection start=%d, end=%d" % \
+ (startOffset, endOffset))
+
+ selectedText = textObj.getText(startOffset, endOffset)
+ debug.println(debug.LEVEL_FINEST,
+ "getSelectedText: selected text=<%s>" % selectedText)
+
+ if i > 0:
+ textContents += " "
+ textContents += selectedText
+
+ return [textContents, startOffset, endOffset]
+
+ def setCaretOffset(self, obj, offset):
+ """Set the caret offset on a given accessible. Similar to
+ Accessible.setCaretOffset()
+
+ Arguments:
+ - obj: Given accessible object.
+ - offset: Offset to hich to set the caret.
+ """
+ try:
+ texti = obj.queryText()
+ except:
+ return None
+
+ texti.setCaretOffset(offset)
+
+ def substring(self, obj, startOffset, endOffset):
+ """Returns the substring of the given object's text specialization.
+
+ Arguments:
+ - obj: an accessible supporting the accessible text specialization
+ - startOffset: the starting character position
+ - endOffset: the ending character position. Note that an end offset
+ of -1 means the last character
+ """
+
+ try:
+ text = obj.queryText()
+ except:
+ return ""
+
+ return text.getText(startOffset, endOffset)
+
+ def textAttributes(self, acc, offset, get_defaults=False):
+ """Get the text attributes run for a given offset in a given accessible
+
+ Arguments:
+ - acc: An accessible.
+ - offset: Offset in the accessible's text for which to retrieve the
+ attributes.
+ - get_defaults: Get the default attributes as well as the unique ones.
+ Default is True
+
+ Returns a dictionary of attributes, a start offset where the attributes
+ begin, and an end offset. Returns ({}, 0, 0) if the accessible does not
+ supprt the text attribute.
+ """
+
+ rv = {}
+ try:
+ text = acc.queryText()
+ except:
+ return rv, 0, 0
+
+ if get_defaults:
+ stringAndDict = \
+ self.stringToKeysAndDict(text.getDefaultAttributes())
+ rv.update(stringAndDict[1])
+
+ attrString, start, end = text.getAttributes(offset)
+ stringAndDict = self.stringToKeysAndDict(attrString)
+ rv.update(stringAndDict[1])
+
+ return rv, start, end
+
+ def unicodeText(self, obj):
+ """Returns the unicode text for an object or None if the object
+ doesn't implement the accessible text specialization.
+ """
+
+ text = self.queryNonEmptyText(obj)
+ if text:
+ unicodeText = text.getText(0, -1).decode("UTF-8")
+ else:
+ unicodeText = None
+
+ return unicodeText
+
+ def willEchoCharacter(self, event):
+ """Given a keyboard event containing an alphanumeric key,
+ determine if the script is likely to echo it as a character.
+ """
+
+ if not orca_state.locusOfFocus or not settings.enableEchoByCharacter:
+ return False
+
+ # The check here in English is something like this: "If this
+ # character echo is enabled, then character echo is likely to
+ # happen if the locus of focus is a focusable editable text
+ # area or terminal and neither of the Ctrl, Alt, or Orca
+ # modifiers are pressed. If that's the case, then character
+ # echo will kick in for us."
+ #
+ return (self.isTextArea(orca_state.locusOfFocus)\
+ or orca_state.locusOfFocus.getRole() \
+ == pyatspi.ROLE_ENTRY) \
+ and (orca_state.locusOfFocus.getRole() \
+ == pyatspi.ROLE_TERMINAL \
+ or (not self.isReadOnlyTextArea(orca_state.locusOfFocus) \
+ and (orca_state.locusOfFocus.getState().contains( \
+ pyatspi.STATE_FOCUSABLE)))) \
+ and not (event.modifiers & settings.ORCA_CTRL_MODIFIER_MASK)
+
+ def wordAtCoords(self, acc, x, y):
+ """Get the word at the given coords in the accessible.
+
+ Arguments:
+ - acc: Accessible that supports the Text interface.
+ - x: X coordinate.
+ - y: Y coordinate.
+
+ Returns a tuple containing the word, start offset, and end offset.
+ """
+
+ try:
+ ti = acc.queryText()
+ except NotImplementedError:
+ return '', 0, 0
+
+ text_contents = ti.getText(0, self._script.getTextEndOffset(ti))
+ line_offsets = []
+ start_offset = 0
+ while True:
+ try:
+ end_offset = text_contents.index('\n', start_offset)
+ except ValueError:
+ line_offsets.append((start_offset, len(text_contents)))
+ break
+ line_offsets.append((start_offset, end_offset))
+ start_offset = end_offset + 1
+ for start, end in line_offsets:
+ bx, by, bw, bh = \
+ ti.getRangeExtents(start, end, pyatspi.DESKTOP_COORDS)
+ bb = mouse_review.BoundingBox(bx, by, bw, bh)
+ if bb.isInBox(x, y):
+ start_offset = 0
+ word_offsets = []
+ while True:
+ try:
+ end_offset = \
+ text_contents[start:end].index(' ', start_offset)
+ except ValueError:
+ word_offsets.append((start_offset,
+ len(text_contents[start:end])))
+ break
+ word_offsets.append((start_offset, end_offset))
+ start_offset = end_offset + 1
+ for a, b in word_offsets:
+ bx, by, bw, bh = \
+ ti.getRangeExtents(start+a, start+b,
+ pyatspi.DESKTOP_COORDS)
+ bb = mouse_review.BoundingBox(bx, by, bw, bh)
+ if bb.isInBox(x, y):
+ return text_contents[start+a:start+b], start+a, start+b
+
+ return '', 0, 0
+
+ #########################################################################
+ # #
+ # Debugging and Reporting Utilities #
+ # #
+ #########################################################################
+
+ def _isInterestingObj(self, obj):
+ import inspect
+
+ interesting = False
+
+ if getattr(obj, "__class__", None):
+ name = obj.__class__.__name__
+ if name not in ["function",
+ "type",
+ "list",
+ "dict",
+ "tuple",
+ "wrapper_descriptor",
+ "module",
+ "method_descriptor",
+ "member_descriptor",
+ "instancemethod",
+ "builtin_function_or_method",
+ "frame",
+ "classmethod",
+ "classmethod_descriptor",
+ "_Environ",
+ "MemoryError",
+ "_Printer",
+ "_Helper",
+ "getset_descriptor",
+ "weakref",
+ "property",
+ "cell",
+ "staticmethod",
+ "EventListener",
+ "KeystrokeListener",
+ "KeyBinding",
+ "InputEventHandler",
+ "Rolename"]:
+ try:
+ filename = inspect.getabsfile(obj.__class__)
+ if filename.index("orca"):
+ interesting = True
+ except:
+ pass
+
+ return interesting
+
+ def _detectCycle(self, obj, visitedObjs, indent=""):
+ """Attempts to discover a cycle in object references."""
+
+ # [[[TODO: WDW - not sure this really works.]]]
+
+ import gc
+ visitedObjs.append(obj)
+ for referent in gc.get_referents(obj):
+ try:
+ if visitedObjs.index(referent):
+ if self._isInterestingObj(referent):
+ print indent, "CYCLE!!!!", `referent`
+ break
+ except:
+ pass
+ self._detectCycle(referent, visitedObjs, " " + indent)
+ visitedObjs.remove(obj)
+
+ def printAncestry(self, child):
+ """Prints a hierarchical view of a child's ancestry."""
+
+ if not child:
+ return
+
+ ancestorList = [child]
+ parent = child.parent
+ while parent and (parent.parent != parent):
+ ancestorList.insert(0, parent)
+ parent = parent.parent
+
+ indent = ""
+ for ancestor in ancestorList:
+ line = indent + "+- " + debug.getAccessibleDetails(ancestor)
+ debug.println(debug.LEVEL_OFF, line)
+ print line
+ indent += " "
+
+ def printApps(self):
+ """Prints a list of all applications to stdout."""
+
+ apps = self.knownApplications()
+ line = "There are %d Accessible applications" % len(apps)
+ debug.println(debug.LEVEL_OFF, line)
+ print line
+ for app in apps:
+ line = debug.getAccessibleDetails(app, " App: ", False)
+ debug.println(debug.LEVEL_OFF, line)
+ print line
+ for child in app:
+ line = debug.getAccessibleDetails(child, " Window: ", False)
+ debug.println(debug.LEVEL_OFF, line)
+ print line
+ if child.parent != app:
+ debug.println(debug.LEVEL_OFF,
+ " WARNING: child's parent is not app!!!")
+
+ return True
+
+ def printHierarchy(self, root, ooi, indent="",
+ onlyShowing=True, omitManaged=True):
+ """Prints the accessible hierarchy of all children
+
+ Arguments:
+ -indent: Indentation string
+ -root: Accessible where to start
+ -ooi: Accessible object of interest
+ -onlyShowing: If True, only show children painted on the screen
+ -omitManaged: If True, omit children that are managed descendants
+ """
+
+ if not root:
+ return
+
+ if root == ooi:
+ line = indent + "(*) " + debug.getAccessibleDetails(root)
+ else:
+ line = indent + "+- " + debug.getAccessibleDetails(root)
+
+ debug.println(debug.LEVEL_OFF, line)
+ print line
+
+ rootManagesDescendants = root.getState().contains(
+ pyatspi.STATE_MANAGES_DESCENDANTS)
+
+ for child in root:
+ if child == root:
+ line = indent + " " + "WARNING CHILD == PARENT!!!"
+ debug.println(debug.LEVEL_OFF, line)
+ print line
+ elif not child:
+ line = indent + " " + "WARNING Child IS NONE!!!"
+ debug.println(debug.LEVEL_OFF, line)
+ print line
+ elif self.validParent(child) != root:
+ line = indent + " " + "WARNING CHILD.PARENT != PARENT!!!"
+ debug.println(debug.LEVEL_OFF, line)
+ print line
+ else:
+ paint = (not onlyShowing) or (onlyShowing and \
+ child.getState().contains(pyatspi.STATE_SHOWING))
+ paint = paint \
+ and ((not omitManaged) \
+ or (omitManaged and not rootManagesDescendants))
+
+ if paint:
+ self.printHierarchy(child,
+ ooi,
+ indent + " ",
+ onlyShowing,
+ omitManaged)
+
+ def scriptInfo(self):
+ """Output useful information on the current script via speech
+ and braille. This information will be helpful to script writers.
+ """
+
+ infoString = "SCRIPT INFO: Script name='%s'" % self._script.name
+ app = orca_state.locusOfFocus.getApplication()
+ if orca_state.locusOfFocus and app:
+ infoString += " Application name='%s'" \
+ % app.name
+
+ try:
+ infoString += " Toolkit name='%s'" \
+ % app.toolkitName
+ except:
+ infoString += " Toolkit unknown"
+
+ try:
+ infoString += " Version='%s'" \
+ % app.version
+ except:
+ infoString += " Version unknown"
+
+ debug.println(debug.LEVEL_OFF, infoString)
+ print infoString
+ self._script.speakMessage(infoString)
+ self._script.displayBrailleMessage(infoString)
+
+ return True
+
+ #########################################################################
+ # #
+ # Miscellaneous Utilities #
+ # #
+ #########################################################################
+
+ def _addRepeatSegment(self, segment, line, respectPunctuation=True):
+ """Add in the latest line segment, adjusting for repeat characters
+ and punctuation.
+
+ Arguments:
+ - segment: the segment of repeated characters.
+ - line: the current built-up line to characters to speak.
+ - respectPunctuation: if False, ignore punctuation level.
+
+ Returns: the current built-up line plus the new segment, after
+ adjusting for repeat character counts and punctuation.
+ """
+
+ import punctuation_settings
+ import chnames
+
+ style = settings.verbalizePunctuationStyle
+ isPunctChar = True
+ try:
+ level, action = punctuation_settings.getPunctuationInfo(segment[0])
+ except:
+ isPunctChar = False
+ count = len(segment)
+ if (count >= settings.repeatCharacterLimit) \
+ and (not segment[0] in self._script.whitespace):
+ if (not respectPunctuation) \
+ or (isPunctChar and (style <= level)):
+ repeatChar = chnames.getCharacterName(segment[0])
+ # Translators: Orca will tell you how many characters
+ # are repeated on a line of text. For example: "22
+ # space characters". The %d is the number and the %s
+ # is the spoken word for the character.
+ #
+ line += " " \
+ + ngettext("%(count)d %(repeatChar)s character",
+ "%(count)d %(repeatChar)s characters",
+ count) \
+ % {"count" : count, "repeatChar": repeatChar}
+ else:
+ line += segment
+ else:
+ line += segment
+
+ return line
+
+ def _pronunciationForSegment(self, segment):
+ """Adjust the word segment to potentially replace it with what
+ those words actually sound like. Two pronunciation dictionaries
+ are checked. First the application specific one (which might be
+ empty), then the default (global) one.
+
+ Arguments:
+ - segment: the string to adjust for words in the pronunciation
+ dictionaries.
+
+ Returns: a new word segment adjusted for words found in the
+ pronunciation dictionaries, or the original word segment if there
+ was no dictionary entry.
+ """
+
+ import pronunciation_dict
+
+ newSegment = pronunciation_dict.getPronunciation(
+ segment, self._script.app_pronunciation_dict)
+ if newSegment == segment:
+ newSegment = pronunciation_dict.getPronunciation(segment)
+
+ return newSegment
+
+ def adjustForLinks(self, obj, line, startOffset):
+ """Adjust line to include the word "link" after any hypertext links.
+
+ 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.
+ """
+
+ import punctuation_settings
+
+ line = line.decode("UTF-8")
+ endOffset = startOffset + len(line)
+
+ try:
+ hyperText = obj.queryHypertext()
+ nLinks = hyperText.getNLinks()
+ except:
+ nLinks = 0
+
+ adjustedLine = list(line)
+ for n in range(nLinks, 0, -1):
+ link = hyperText.getLink(n - 1)
+
+ # We only care about links in the string, line:
+ #
+ if startOffset < link.endIndex < endOffset:
+ index = link.endIndex - startOffset
+ elif startOffset <= link.startIndex < endOffset:
+ index = len(line) - 1
+ else:
+ continue
+
+ # Translators: this indicates that this piece of
+ # text is a hypertext link.
+ #
+ linkString = " " + _("link")
+
+ # If the link was not followed by a whitespace or punctuation
+ # character, then add in a space to make it more presentable.
+ #
+ nextChar = adjustedLine[index]
+ if not (nextChar in self._script.whitespace \
+ or punctuation_settings.getPunctuationInfo(nextChar)):
+ linkString += " "
+ adjustedLine[index:index] = linkString
+
+ return "".join(adjustedLine).encode("UTF-8")
+
+ def adjustForPronunciation(self, line):
+ """Adjust the line to replace words in the pronunciation dictionary,
+ with what those words actually sound like.
+
+ Arguments:
+ - line: the string to adjust for words in the pronunciation dictionary.
+
+ Returns: a new line adjusted for words found in the pronunciation
+ dictionary.
+ """
+
+ newLine = ""
+ words = self.WORDS_RE.split(line.decode("UTF-8"))
+ for word in words:
+ if word.isalnum():
+ word = self._pronunciationForSegment(word)
+ newLine += word
+
+ if line != newLine:
+ debug.println(debug.LEVEL_FINEST,
+ "adjustForPronunciation: \n From '%s'\n To '%s'" \
+ % (line, newLine))
+ return newLine.encode("UTF-8")
+ else:
+ return line
+
+ def adjustForRepeats(self, line):
+ """Adjust line to include repeat character counts. As some people
+ will want this and others might not, there is a setting in
+ settings.py that determines whether this functionality is enabled.
+
+ repeatCharacterLimit = <n>
+
+ If <n> is 0, then there would be no repeat characters.
+ Otherwise <n> would be the number of same characters (or more)
+ in a row that cause the repeat character count output.
+ If the value is set to 1, 2 or 3 then it's treated as if it was
+ zero. In other words, no repeat character count is given.
+
+ Arguments:
+ - line: the string to adjust for repeat character counts.
+
+ Returns: a new line adjusted for repeat character counts (if enabled).
+ """
+
+ line = line.decode("UTF-8")
+
+ if (len(line) < 4) or (settings.repeatCharacterLimit < 4):
+ return line.encode("UTF-8")
+
+ newLine = u''
+ segment = lastChar = line[0]
+
+ multipleChars = False
+ for i in range(1, len(line)):
+ if line[i] == lastChar:
+ segment += line[i]
+ else:
+ multipleChars = True
+ newLine = self._addRepeatSegment(segment, newLine)
+ segment = line[i]
+
+ lastChar = line[i]
+
+ newLine = self._addRepeatSegment(segment, newLine, multipleChars)
+
+ # Pylint is confused and flags this with the following error:
+ #
+ # E1103:5188:Script.adjustForRepeats: Instance of 'True' has
+ # no 'encode' member (but some types could not be inferred)
+ #
+ # We know newLine is a unicode string, so we'll just tell pylint
+ # that we know what we are doing.
+ #
+ # pylint: disable-msg=E1103
+
+ return newLine.encode("UTF-8")
+
+ @staticmethod
+ def absoluteMouseCoordinates():
+ """Gets the absolute position of the mouse pointer."""
+
+ import gtk
+ rootWindow = gtk.Window().get_screen().get_root_window()
+ x, y, modifiers = rootWindow.get_pointer()
+
+ return x, y
+
+ @staticmethod
+ def appendString(text, newText, delimiter=" "):
+ """Appends the newText to the given text with the delimiter in between
+ and returns the new string. Edge cases, such as no initial text or
+ no newText, are handled gracefully."""
+
+ if not newText or len(newText) == 0:
+ return text
+ elif text and len(text):
+ return text + delimiter + newText
+ else:
+ return newText
+
+ 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
+ normal end-of-sentence punctuation characters.
+
+ Arguments:
+ - currentChar: the current character
+ - previousChar: the previous character
+
+ Returns True if the given character is a sentence delimiter.
+ """
+
+ if not isinstance(currentChar, unicode):
+ currentChar = currentChar.decode("UTF-8")
+
+ if not isinstance(previousChar, unicode):
+ previousChar = previousChar.decode("UTF-8")
+
+ if currentChar == '\r' or currentChar == '\n':
+ return True
+
+ return currentChar in self._script.whitespace \
+ and previousChar in '!.?:;'
+
+ def isWordDelimiter(self, character):
+ """Returns True if the given character is a word delimiter.
+
+ Arguments:
+ - character: the character in question
+
+ Returns True if the given character is a word delimiter.
+ """
+
+ if not isinstance(character, unicode):
+ character = character.decode("UTF-8")
+
+ return character in self._script.whitespace \
+ or character in '!*+,-./:;<=>? [\]^_{|}' \
+ or character == self._script.NO_BREAK_SPACE_CHARACTER
+
+ @staticmethod
+ def isVisibleRegion(ax, ay, awidth, aheight, bx, by, bwidth, bheight):
+ """Returns true if any portion of region 'a' is in region 'b'"""
+
+ highestBottom = min(ay + aheight, by + bheight)
+ lowestTop = max(ay, by)
+ leftMostRightEdge = min(ax + awidth, bx + bwidth)
+ rightMostLeftEdge = max(ax, bx)
+
+ if lowestTop <= highestBottom \
+ and rightMostLeftEdge <= leftMostRightEdge:
+ return True
+ elif aheight == 0:
+ if awidth == 0:
+ return lowestTop == highestBottom \
+ and leftMostRightEdge == rightMostLeftEdge
+ else:
+ return leftMostRightEdge <= rightMostLeftEdge
+ elif awidth == 0:
+ return lowestTop <= highestBottom
+
+ return False
+
+ def mnemonicShortcutAccelerator(self, obj):
+ """Gets the mnemonic, accelerator string and possibly shortcut
+ for the given object. These are based upon the first accessible
+ action for the object.
+
+ Arguments:
+ - obj: the Accessible object
+
+ Returns: list containing strings: [mnemonic, shortcut, accelerator]
+ """
+
+ try:
+ return self._script.generatorCache[self.KEY_BINDING][obj]
+ except:
+ if not self._script.generatorCache.has_key(self.KEY_BINDING):
+ self._script.generatorCache[self.KEY_BINDING] = {}
+
+ try:
+ action = obj.queryAction()
+ except NotImplementedError:
+ self._script.generatorCache[self.KEY_BINDING][obj] = ["", "", ""]
+ return self._script.generatorCache[self.KEY_BINDING][obj]
+
+ # Action is a string in the format, where the mnemonic and/or
+ # accelerator can be missing.
+ #
+ # <mnemonic>;<full-path>;<accelerator>
+ #
+ # The keybindings in <full-path> should be separated by ":"
+ #
+ bindingStrings = action.getKeyBinding(0).decode("UTF-8").split(';')
+
+ if len(bindingStrings) == 3:
+ mnemonic = bindingStrings[0]
+ fullShortcut = bindingStrings[1]
+ accelerator = bindingStrings[2]
+ elif len(bindingStrings) > 0:
+ mnemonic = ""
+ fullShortcut = bindingStrings[0]
+ try:
+ accelerator = bindingStrings[1]
+ except:
+ accelerator = ""
+ else:
+ mnemonic = ""
+ fullShortcut = ""
+ accelerator = ""
+
+ fullShortcut = fullShortcut.replace("<","")
+ fullShortcut = fullShortcut.replace(">"," ")
+ fullShortcut = fullShortcut.replace(":"," ").strip()
+
+ # If the accelerator or mnemonic strings includes a Space,
+ # make sure we speak it.
+ #
+ if mnemonic.endswith(" "):
+ # Translators: this is the spoken word for the space character
+ #
+ mnemonic += _("space")
+ mnemonic = mnemonic.replace("<","")
+ mnemonic = mnemonic.replace(">"," ").strip()
+
+ if accelerator.endswith(" "):
+ # Translators: this is the spoken word for the space character
+ #
+ accelerator += _("space")
+ accelerator = accelerator.replace("<","")
+ accelerator = accelerator.replace(">"," ").strip()
+
+ debug.println(debug.LEVEL_FINEST, "script_utilities.getKeyBinding: " \
+ + repr([mnemonic, fullShortcut, accelerator]))
+
+ self._script.generatorCache[self.KEY_BINDING][obj] = \
+ [mnemonic, fullShortcut, accelerator]
+ return self._script.generatorCache[self.KEY_BINDING][obj]
+
+ @staticmethod
+ def stringToKeysAndDict(string):
+ """Converts a string made up of a series of <key>:<value>; pairs
+ into a dictionary of keys and values. Text before the colon is the
+ key and text afterwards is the value. The final semi-colon, if
+ found, is ignored.
+
+ Arguments:
+ - string: the string of tokens containing <key>:<value>; pairs.
+
+ Returns a list containing two items:
+ A list of the keys in the order they were extracted from the
+ string and a dictionary of key/value items.
+ """
+
+ try:
+ items = [s.strip() for s in string.split(";")]
+ items = filter(lambda item: len(item.split(':')) == 2, items)
+ keys = map(lambda item: item.split(':')[0].strip(), items)
+ dictionary = dict(map(lambda item: item.split(':'), items))
+ except:
+ return [], {}
+
+ return [keys, dictionary]
+
+ def textForValue(self, obj):
+ """Returns the text to be displayed for the object's current value.
+
+ Arguments:
+ - obj: the Accessible object that may or may not have a value.
+
+ Returns a string representing the value.
+ """
+
+ # Use ARIA "valuetext" attribute if present. See
+ # http://bugzilla.gnome.org/show_bug.cgi?id=552965
+ #
+ attributes = obj.getAttributes()
+ for attribute in attributes:
+ if attribute.startswith("valuetext"):
+ return attribute[10:]
+
+ try:
+ value = obj.queryValue()
+ except NotImplementedError:
+ return ""
+
+ # OK, this craziness is all about trying to figure out the most
+ # meaningful formatting string for the floating point values.
+ # The number of places to the right of the decimal point should
+ # be set by the minimumIncrement, but the minimumIncrement isn't
+ # always set. So...we'll default the minimumIncrement to 1/100
+ # of the range. But, if max == min, then we'll just go for showing
+ # them off to two meaningful digits.
+ #
+ try:
+ minimumIncrement = value.minimumIncrement
+ except:
+ minimumIncrement = 0.0
+
+ if minimumIncrement == 0.0:
+ minimumIncrement = (value.maximumValue - value.minimumValue) \
+ / 100.0
+
+ try:
+ decimalPlaces = max(0, -math.log10(minimumIncrement))
+ except:
+ try:
+ decimalPlaces = max(0, -math.log10(value.minimumValue))
+ except:
+ try:
+ decimalPlaces = max(0, -math.log10(value.maximumValue))
+ except:
+ decimalPlaces = 0
+
+ formatter = "%%.%df" % decimalPlaces
+ valueString = formatter % value.currentValue
+ #minString = formatter % value.minimumValue
+ #maxString = formatter % value.maximumValue
+
+ # [[[TODO: WDW - probably want to do this as a percentage at some
+ # point? Logged as bugzilla bug 319743.]]]
+ #
+ return valueString
+
+ @staticmethod
+ def unicodeValueString(character):
+ """ Returns a four hex digit representation of the given character
+
+ Arguments:
+ - The character to return representation
+
+ Returns a string representaition of the given character unicode vlue
+ """
+
+ try:
+ if not isinstance(character, unicode):
+ character = character.decode('UTF-8')
+ return "%04x" % ord(character)
+ except:
+ debug.printException(debug.LEVEL_WARNING)
+ return ""
diff --git a/src/orca/scripts/apps/Banshee/Makefile.am b/src/orca/scripts/apps/Banshee/Makefile.am
index cc5291f..fa90363 100644
--- a/src/orca/scripts/apps/Banshee/Makefile.am
+++ b/src/orca/scripts/apps/Banshee/Makefile.am
@@ -3,6 +3,7 @@ orca_pathdir=$(pyexecdir)
orca_python_PYTHON = \
__init__.py \
script.py \
+ script_utilities.py \
speech_generator.py \
formatting.py
diff --git a/src/orca/scripts/apps/Banshee/script.py b/src/orca/scripts/apps/Banshee/script.py
index b05f874..a5c419f 100644
--- a/src/orca/scripts/apps/Banshee/script.py
+++ b/src/orca/scripts/apps/Banshee/script.py
@@ -1,7 +1,7 @@
import orca.default as default
import orca.orca_state as orca_state
-import pyatspi
+from script_utilities import Utilities
from speech_generator import SpeechGenerator
from formatting import Formatting
@@ -16,7 +16,6 @@ class Script(default.Script):
default.Script.__init__(self, app)
self._last_seek_value = 0
-
def getSpeechGenerator(self):
"""Returns the speech generator for this script.
"""
@@ -26,50 +25,28 @@ class Script(default.Script):
"""Returns the formatting strings for this script."""
return Formatting(self)
- def _formatDuration(self, s):
- seconds = '%02d' % (s%60)
- minutes = '%02d' % (s/60)
- hours = '%02d' % (s/3600)
-
- duration = [minutes, seconds]
-
- if hours != '00':
- duration.insert(0, hours)
-
- return ':'.join(duration)
+ def getUtilities(self):
+ """Returns the utilites for this script."""
- def _isSeekSlider(self, obj):
- return bool(pyatspi.findAncestor(
- obj, lambda x: x.getRole() == pyatspi.ROLE_TOOL_BAR))
+ return Utilities(self)
def visualAppearanceChanged(self, event, obj):
if event.type == 'object:property-change:accessible-value' and \
- self._isSeekSlider(obj):
+ self.utilities.isSeekSlider(obj):
try:
value = obj.queryValue()
except NotImplementedError:
- return default.Script.getTextForValue(self, obj)
+ return default.Script.visualAppearanceChanged(self, event, obj)
current_value = int(value.currentValue)/1000
if current_value in \
range(self._last_seek_value, self._last_seek_value + 4):
- if self.isSameObject(obj, orca_state.locusOfFocus):
+ if self.utilities.isSameObject(obj, orca_state.locusOfFocus):
self.updateBraille(obj)
return
self._last_seek_value = current_value
default.Script.visualAppearanceChanged(self, event, obj)
-
-
- def getTextForValue(self, obj):
- if not self._isSeekSlider(obj):
- return default.Script.getTextForValue(self, obj)
- try:
- value = obj.queryValue()
- except NotImplementedError:
- return default.Script.getTextForValue(self, obj)
- else:
- return self._formatDuration(int(value.currentValue)/1000)
diff --git a/src/orca/scripts/apps/Banshee/script_utilities.py b/src/orca/scripts/apps/Banshee/script_utilities.py
new file mode 100644
index 0000000..f6d8dce
--- /dev/null
+++ b/src/orca/scripts/apps/Banshee/script_utilities.py
@@ -0,0 +1,40 @@
+import pyatspi
+import orca.script_utilities as script_utilities
+
+class Utilities(script_utilities.Utilities):
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ script_utilities.Utilities.__init__(self, script)
+
+ def _formatDuration(self, s):
+ seconds = '%02d' % (s%60)
+ minutes = '%02d' % (s/60)
+ hours = '%02d' % (s/3600)
+
+ duration = [minutes, seconds]
+
+ if hours != '00':
+ duration.insert(0, hours)
+
+ return ':'.join(duration)
+
+ def isSeekSlider(self, obj):
+ return bool(pyatspi.findAncestor(
+ obj, lambda x: x.getRole() == pyatspi.ROLE_TOOL_BAR))
+
+ def textForValue(self, obj):
+ if not self.isSeekSlider(obj):
+ return script_utilities.Utilities.textForValue(self, obj)
+
+ try:
+ value = obj.queryValue()
+ except NotImplementedError:
+ return script_utilities.Utilities.textForValue(self, obj)
+ else:
+ return self._formatDuration(int(value.currentValue)/1000)
diff --git a/src/orca/scripts/apps/Instantbird/Makefile.am b/src/orca/scripts/apps/Instantbird/Makefile.am
index bdbeb1b..9d7ae5e 100644
--- a/src/orca/scripts/apps/Instantbird/Makefile.am
+++ b/src/orca/scripts/apps/Instantbird/Makefile.am
@@ -3,7 +3,8 @@ orca_pathdir=$(pyexecdir)
orca_python_PYTHON = \
__init__.py \
chat.py \
- script.py
+ script.py \
+ script_utilities.py
orca_pythondir=$(pyexecdir)/orca/scripts/apps/Instantbird
diff --git a/src/orca/scripts/apps/Instantbird/chat.py b/src/orca/scripts/apps/Instantbird/chat.py
index 893b190..03df0e2 100644
--- a/src/orca/scripts/apps/Instantbird/chat.py
+++ b/src/orca/scripts/apps/Instantbird/chat.py
@@ -72,7 +72,8 @@ class Chat(chat.Chat):
#
if event.source.getRole() == pyatspi.ROLE_DOCUMENT_FRAME:
bubble = event.source[event.detail1]
- paragraphs = self._script.findByRole(bubble, pyatspi.ROLE_PARAGRAPH)
+ paragraphs = self._script.utilities.descendantsWithRole(
+ bubble, pyatspi.ROLE_PARAGRAPH)
# If the user opted the non-default, "simple" appearance, then this
# might not be a bubble at all, but a paragraph.
@@ -81,19 +82,12 @@ class Chat(chat.Chat):
paragraphs.append(bubble)
for paragraph in paragraphs:
- try:
- msg = paragraph.queryText().getText(0, -1)
- except:
- pass
- else:
- if msg == self._script.EMBEDDED_OBJECT_CHARACTER:
- # This seems to occur for non-focused conversations.
- #
- try:
- msg = paragraph[0].queryText().getText(0, -1)
- except:
- msg = ""
- string = self._script.appendString(string, msg)
+ msg = self._script.utilities.substring(paragraph, 0, -1)
+ if msg == self._script.EMBEDDED_OBJECT_CHARACTER:
+ # This seems to occur for non-focused conversations.
+ #
+ msg = self._script.utilities.substring(paragraph[0], 0, -1)
+ string = self._script.utilities.appendString(string, msg)
return string
@@ -146,19 +140,19 @@ class Chat(chat.Chat):
"""
name = ""
- ancestor = self._script.getAncestor(obj,
- [pyatspi.ROLE_SCROLL_PANE,
- pyatspi.ROLE_FRAME],
- [pyatspi.ROLE_APPLICATION])
+ ancestor = self._script.utilities.ancestorWithRole(
+ obj,
+ [pyatspi.ROLE_SCROLL_PANE, pyatspi.ROLE_FRAME],
+ [pyatspi.ROLE_APPLICATION])
if ancestor and ancestor.getRole() == pyatspi.ROLE_SCROLL_PANE:
# The scroll pane has a proper labelled by relationship set.
#
- name = self._script.getDisplayedLabel(ancestor)
+ name = self._script.utilities.displayedLabel(ancestor)
if not name:
try:
- text = self._script.getDisplayedText(ancestor)
+ text = self._script.utilities.displayedText(ancestor)
if text.lower().strip() != self._script.name.lower().strip():
name = text
except:
@@ -182,7 +176,8 @@ class Chat(chat.Chat):
# we're in the buddy list instead.
#
if obj and obj.getState().contains(pyatspi.STATE_SHOWING) \
- and self._script.isInActiveApp(obj) and not self.isInBuddyList(obj):
+ and self._script.utilities.isInActiveApp(obj) \
+ and not self.isInBuddyList(obj):
return True
return False
diff --git a/src/orca/scripts/apps/Instantbird/script.py b/src/orca/scripts/apps/Instantbird/script.py
index 826d17f..54eea8a 100644
--- a/src/orca/scripts/apps/Instantbird/script.py
+++ b/src/orca/scripts/apps/Instantbird/script.py
@@ -36,6 +36,7 @@ import orca.settings as settings
import orca.speech as speech
from chat import Chat
+from script_utilities import Utilities
########################################################################
# #
@@ -72,6 +73,11 @@ class Script(Gecko.Script):
return Chat(self, self._buddyListAncestries)
+ def getUtilities(self):
+ """Returns the utilites for this script."""
+
+ return Utilities(self)
+
def getEnabledStructuralNavigationTypes(self):
"""Returns a list of the structural navigation object types
enabled in this script.
@@ -122,38 +128,6 @@ class Script(Gecko.Script):
self.chat.setAppPreferences(prefs)
- def getDisplayedLabel(self, obj):
- """If there is an object labelling the given object, return the
- text being displayed for the object labelling this object.
- Otherwise, return None.
-
- Argument:
- - obj: the object in question
-
- Returns the string of the object labelling this object, or None
- if there is nothing of interest here.
- """
-
- if self.inDocumentContent():
- return Gecko.Script.getDisplayedLabel(self, obj)
-
- return default.Script.getDisplayedLabel(self, obj)
-
- def getDisplayedText(self, obj):
- """Returns the text being displayed for an object.
-
- Arguments:
- - obj: the object
-
- Returns the text being displayed for an object or None if there isn't
- any text being shown.
- """
-
- if self.inDocumentContent(obj):
- return Gecko.Script.getDisplayedText(self, obj)
-
- return default.Script.getDisplayedText(self, obj)
-
def onTextDeleted(self, event):
"""Called whenever text is deleted from an object.
@@ -250,6 +224,7 @@ class Script(Gecko.Script):
# events we need to present text added to the chatroom are
# missing.
#
- allPageTabs = self.findByRole(event.source, pyatspi.ROLE_PAGE_TAB)
+ allPageTabs = self.utilities.descendantsWithRole(
+ event.source, pyatspi.ROLE_PAGE_TAB)
default.Script.onWindowActivated(self, event)
diff --git a/src/orca/scripts/apps/Instantbird/script_utilities.py b/src/orca/scripts/apps/Instantbird/script_utilities.py
new file mode 100644
index 0000000..731051c
--- /dev/null
+++ b/src/orca/scripts/apps/Instantbird/script_utilities.py
@@ -0,0 +1,100 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Commonly-required utility methods needed by -- and potentially
+ customized by -- application and toolkit scripts. They have
+ been pulled out from the scripts because certain scripts had
+ gotten way too large as a result of including these methods."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__ = "LGPL"
+
+import orca.script_utilities as script_utilities
+
+import orca.scripts.toolkits.Gecko as Gecko
+
+#############################################################################
+# #
+# Utilities #
+# #
+#############################################################################
+
+class Utilities(Gecko.Utilities):
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ Gecko.Utilities.__init__(self, script)
+
+ #########################################################################
+ # #
+ # Utilities for finding, identifying, and comparing accessibles #
+ # #
+ #########################################################################
+
+ def displayedLabel(self, obj):
+ """If there is an object labelling the given object, return the
+ text being displayed for the object labelling this object.
+ Otherwise, return None.
+
+ Argument:
+ - obj: the object in question
+
+ Returns the string of the object labelling this object, or None
+ if there is nothing of interest here.
+ """
+
+ if self._script.inDocumentContent():
+ return Gecko.Utilities.displayedLabel(self, obj)
+
+ return script_utilities.Utilities.displayedLabel(self, obj)
+
+ def displayedText(self, obj):
+ """Returns the text being displayed for an object.
+
+ Arguments:
+ - obj: the object
+
+ Returns the text being displayed for an object or None if there isn't
+ any text being shown.
+ """
+
+ if self._script.inDocumentContent(obj):
+ return Gecko.Utilities.displayedText(self, obj)
+
+ return script_utilities.Utilities.displayedText(self, obj)
+
+ #########################################################################
+ # #
+ # Utilities for working with the accessible text interface #
+ # #
+ #########################################################################
+
+ #########################################################################
+ # #
+ # Miscellaneous Utilities #
+ # #
+ #########################################################################
diff --git a/src/orca/scripts/apps/Makefile.am b/src/orca/scripts/apps/Makefile.am
index 35330bc..39ec035 100644
--- a/src/orca/scripts/apps/Makefile.am
+++ b/src/orca/scripts/apps/Makefile.am
@@ -13,6 +13,7 @@ SUBDIRS = \
empathy \
Instantbird \
gajim \
+ ddu \
yelp
orca_pathdir=$(pyexecdir)
@@ -20,7 +21,6 @@ orca_pathdir=$(pyexecdir)
orca_python_PYTHON = \
__init__.py \
acroread.py \
- ddu.py \
ekiga.py \
gdmlogin.py \
gnome-keyring-ask.py \
diff --git a/src/orca/scripts/apps/Thunderbird/Makefile.am b/src/orca/scripts/apps/Thunderbird/Makefile.am
index bf9c7ff..12d8979 100644
--- a/src/orca/scripts/apps/Thunderbird/Makefile.am
+++ b/src/orca/scripts/apps/Thunderbird/Makefile.am
@@ -4,6 +4,7 @@ orca_python_PYTHON = \
__init__.py \
script.py \
script_settings.py \
+ script_utilities.py \
speech_generator.py
orca_pythondir=$(pyexecdir)/orca/scripts/apps/Thunderbird
diff --git a/src/orca/scripts/apps/Thunderbird/script.py b/src/orca/scripts/apps/Thunderbird/script.py
index 52352c6..8d2087f 100644
--- a/src/orca/scripts/apps/Thunderbird/script.py
+++ b/src/orca/scripts/apps/Thunderbird/script.py
@@ -17,8 +17,7 @@
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
# Boston MA 02110-1301 USA.
-""" Custom script for Thunderbird 3.
-"""
+""" Custom script for Thunderbird 3."""
__id__ = "$Id$"
__version__ = "$Revision$"
@@ -41,6 +40,7 @@ import orca.scripts.toolkits.Gecko as Gecko
from orca.orca_i18n import _
from speech_generator import SpeechGenerator
+from script_utilities import Utilities
import script_settings
########################################################################
@@ -86,10 +86,15 @@ class Script(Gecko.Script):
self.textArea = None
def getSpeechGenerator(self):
- """Returns the speech generator for this script.
- """
+ """Returns the speech generator for this script."""
+
return SpeechGenerator(self)
+ def getUtilities(self):
+ """Returns the utilites for this script."""
+
+ return Utilities(self)
+
def getAppPreferencesGUI(self):
"""Return a GtkVBox contain the application unique configuration
GUI items for the current application.
@@ -133,8 +138,8 @@ class Script(Gecko.Script):
script_settings.grabFocusOnAncestor = value
def _debug(self, msg):
- """ Convenience method for printing debug messages
- """
+ """ Convenience method for printing debug messages"""
+
debug.println(self.debugLevel, "Thunderbird.py: "+msg)
def _isSpellCheckListItemFocus(self, event):
@@ -152,7 +157,7 @@ class Script(Gecko.Script):
pyatspi.ROLE_LIST, \
pyatspi.ROLE_DIALOG, \
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
dialog = event.source.parent.parent
# Translators: this is what the name of the spell checking
@@ -237,7 +242,7 @@ class Script(Gecko.Script):
"""
obj = event.source
parent = obj.parent
- top = self.getTopLevel(obj)
+ top = self.utilities.topLevelObject(obj)
consume = False
# Clear the stored autocomplete string.
@@ -258,7 +263,7 @@ class Script(Gecko.Script):
#
if obj.getRole() == pyatspi.ROLE_TABLE_CELL:
table = parent.queryTable()
- row = table.getRowAtIndex(self.getCellIndex(obj))
+ row = table.getRowAtIndex(self.utilities.cellIndex(obj))
for i in range(0, table.nColumns):
acc = table.getAccessibleAt(row, i)
if acc.name:
@@ -288,7 +293,7 @@ class Script(Gecko.Script):
pyatspi.ROLE_INTERNAL_FRAME,
pyatspi.ROLE_FRAME,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
self._debug("onFocus - message text area.")
self.textArea = event.source
@@ -303,7 +308,7 @@ class Script(Gecko.Script):
rolesList = [pyatspi.ROLE_ENTRY, \
pyatspi.ROLE_DIALOG, \
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(obj, rolesList):
+ if self.utilities.hasMatchingHierarchy(obj, rolesList):
dialog = obj.parent
# Translators: this is what the name of the spell checking
@@ -388,7 +393,7 @@ class Script(Gecko.Script):
pyatspi.ROLE_TREE_TABLE,
pyatspi.ROLE_SCROLL_PANE,
pyatspi.ROLE_SCROLL_PANE]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
string = orca_state.lastNonModifierKeyEvent.event_string
if string == "Delete":
@@ -402,7 +407,7 @@ class Script(Gecko.Script):
pyatspi.ROLE_INTERNAL_FRAME, \
pyatspi.ROLE_FRAME, \
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
string = orca_state.lastNonModifierKeyEvent.event_string
if string == "Delete":
@@ -510,7 +515,7 @@ class Script(Gecko.Script):
pyatspi.ROLE_INTERNAL_FRAME, \
pyatspi.ROLE_FRAME, \
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
string = orca_state.lastNonModifierKeyEvent.event_string
if string == "Delete":
@@ -529,7 +534,7 @@ class Script(Gecko.Script):
pyatspi.ROLE_LIST, \
pyatspi.ROLE_DIALOG, \
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(obj, rolesList):
+ if self.utilities.hasMatchingHierarchy(obj, rolesList):
dialog = obj.parent.parent
# Translators: this is what the name of the spell checking
@@ -539,7 +544,7 @@ class Script(Gecko.Script):
#
if dialog.name.startswith(_("Check Spelling")):
if obj.getIndexInParent() == 0:
- badWord = self.getDisplayedText(dialog[1])
+ badWord = self.utilities.displayedText(dialog[1])
if self.textArea != None:
# If we have a handle to the Thunderbird message text
@@ -547,7 +552,7 @@ class Script(Gecko.Script):
# create a list of all the words found in them.
#
allTokens = []
- text = self.getText(self.textArea, 0, -1)
+ text = self.utilities.substring(self.textArea, 0, -1)
tokens = text.split()
allTokens += tokens
self.speakMisspeltWord(allTokens, badWord)
@@ -624,24 +629,6 @@ class Script(Gecko.Script):
return [obj, offset]
- def getDocumentFrame(self):
- """Returns the document frame that holds the content being shown.
- Overridden here because multiple open messages are not arranged
- in tabs like they are in Firefox."""
-
- if self.inFindToolbar():
- return Gecko.Script.getDocumentFrame(self)
-
- obj = orca_state.locusOfFocus
- while obj:
- role = obj.getRole()
- if role in [pyatspi.ROLE_DOCUMENT_FRAME, pyatspi.ROLE_EMBEDDED]:
- return obj
- else:
- obj = obj.parent
-
- return None
-
def toggleFlatReviewMode(self, inputEvent=None):
"""Toggles between flat review mode and focus tracking mode."""
@@ -659,9 +646,8 @@ class Script(Gecko.Script):
Returns True is this is something like the Subject: entry
"""
result = obj and obj.getRole() == pyatspi.ROLE_ENTRY \
- and None == self.getAncestor(obj,
- [pyatspi.ROLE_DOCUMENT_FRAME],
- [pyatspi.ROLE_FRAME])
+ and not self.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_DOCUMENT_FRAME], [pyatspi.ROLE_FRAME])
return result
def isEditableMessage(self, obj):
diff --git a/src/orca/scripts/apps/Thunderbird/script_utilities.py b/src/orca/scripts/apps/Thunderbird/script_utilities.py
new file mode 100644
index 0000000..85d6624
--- /dev/null
+++ b/src/orca/scripts/apps/Thunderbird/script_utilities.py
@@ -0,0 +1,88 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Commonly-required utility methods needed by -- and potentially
+ customized by -- application and toolkit scripts. They have
+ been pulled out from the scripts because certain scripts had
+ gotten way too large as a result of including these methods."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__ = "LGPL"
+
+import pyatspi
+
+import orca.orca_state as orca_state
+
+import orca.scripts.toolkits.Gecko as Gecko
+
+#############################################################################
+# #
+# Utilities #
+# #
+#############################################################################
+
+class Utilities(Gecko.Utilities):
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ Gecko.Utilities.__init__(self, script)
+
+ #########################################################################
+ # #
+ # Utilities for finding, identifying, and comparing accessibles #
+ # #
+ #########################################################################
+
+ def documentFrame(self):
+ """Returns the document frame that holds the content being shown.
+ Overridden here because multiple open messages are not arranged
+ in tabs like they are in Firefox."""
+
+ if self._script.inFindToolbar():
+ return Gecko.Utilities.documentFrame(self)
+
+ obj = orca_state.locusOfFocus
+ while obj:
+ role = obj.getRole()
+ if role in [pyatspi.ROLE_DOCUMENT_FRAME, pyatspi.ROLE_EMBEDDED]:
+ return obj
+ else:
+ obj = obj.parent
+
+ return None
+
+ #########################################################################
+ # #
+ # Utilities for working with the accessible text interface #
+ # #
+ #########################################################################
+
+ #########################################################################
+ # #
+ # Miscellaneous Utilities #
+ # #
+ #########################################################################
diff --git a/src/orca/scripts/apps/Thunderbird/speech_generator.py b/src/orca/scripts/apps/Thunderbird/speech_generator.py
index 51f1f37..13f7b59 100644
--- a/src/orca/scripts/apps/Thunderbird/speech_generator.py
+++ b/src/orca/scripts/apps/Thunderbird/speech_generator.py
@@ -86,7 +86,7 @@ class SpeechGenerator(Gecko.SpeechGenerator):
# off stuff like this, but we're forced to do so in this case.
#
if obj.name.startswith(_("Check Spelling")) \
- and self._script.isDesiredFocusedItem(
+ and self._script.utilities.hasMatchingHierarchy(
obj, [pyatspi.ROLE_DIALOG,
pyatspi.ROLE_APPLICATION]):
pass
diff --git a/src/orca/scripts/apps/acroread.py b/src/orca/scripts/apps/acroread.py
index 1364a1c..ff87af0 100644
--- a/src/orca/scripts/apps/acroread.py
+++ b/src/orca/scripts/apps/acroread.py
@@ -223,7 +223,7 @@ class Script(default.Script):
pyatspi.ROLE_UNKNOWN,
pyatspi.ROLE_UNKNOWN,
pyatspi.ROLE_TABLE]
- if self.isDesiredFocusedItem(obj, rolesList):
+ if self.utilities.hasMatchingHierarchy(obj, rolesList):
table = obj.parent.parent.parent
rows = table.childCount
columns = table[0].childCount
@@ -330,9 +330,11 @@ class Script(default.Script):
try:
while obj.getRole() != pyatspi.ROLE_DRAWING_AREA:
obj = obj.parent
- if self.isDesiredFocusedItem(obj, rolesList):
+ if self.utilities.hasMatchingHierarchy(obj, rolesList):
inFindToolbar = True
- self.findToolbarName = self.getFrame(obj).name
+ frame = self.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_FRAME], [])
+ self.findToolbarName = frame.name
except:
pass
@@ -470,7 +472,8 @@ class Script(default.Script):
utterances = self.speechGenerator.generateSpeech(newLocusOfFocus)
adjustedUtterances = []
for utterance in utterances:
- adjustedUtterances.append(self.adjustForRepeats(utterance))
+ adjustedUtterances.append(
+ self.utilities.adjustForRepeats(utterance))
speech.speak(adjustedUtterances)
self.displayBrailleForObject(newLocusOfFocus)
orca.setLocusOfFocus(
@@ -678,14 +681,14 @@ class Script(default.Script):
if lastWord == word:
return
- if self.getLinkIndex(obj, offset) >= 0:
+ if self.utilities.linkIndex(obj, offset) >= 0:
voice = self.voices[settings.HYPERLINK_VOICE]
elif word.decode("UTF-8").isupper():
voice = self.voices[settings.UPPERCASE_VOICE]
else:
voice = self.voices[settings.DEFAULT_VOICE]
- word = self.adjustForRepeats(word)
+ word = self.utilities.adjustForRepeats(word)
orca_state.lastWord = word
speech.speak(word, voice)
self.speakTextSelectionState(obj, startOffset, endOffset)
diff --git a/src/orca/scripts/apps/ddu/Makefile.am b/src/orca/scripts/apps/ddu/Makefile.am
new file mode 100644
index 0000000..d21f79f
--- /dev/null
+++ b/src/orca/scripts/apps/ddu/Makefile.am
@@ -0,0 +1,8 @@
+orca_pathdir=$(pyexecdir)
+
+orca_python_PYTHON = \
+ __init__.py \
+ script.py \
+ script_utilities.py
+
+orca_pythondir=$(pyexecdir)/orca/scripts/apps/ddu
diff --git a/src/orca/scripts/apps/ddu/__init__.py b/src/orca/scripts/apps/ddu/__init__.py
new file mode 100644
index 0000000..eca2ae7
--- /dev/null
+++ b/src/orca/scripts/apps/ddu/__init__.py
@@ -0,0 +1,23 @@
+# Orca
+#
+# Copyright 2005-2009 Sun Microsystems Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Custom script for Packagemanager."""
+
+from script import Script
+
diff --git a/src/orca/scripts/apps/ddu/script.py b/src/orca/scripts/apps/ddu/script.py
new file mode 100644
index 0000000..99b3572
--- /dev/null
+++ b/src/orca/scripts/apps/ddu/script.py
@@ -0,0 +1,154 @@
+# Orca
+#
+# Copyright 2009 Sun Microsystems Inc.
+# Copyright 2010 Joanmarie Diggs
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Custom script for the Device Driver Utility."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2009 Sun Microsystems Inc." \
+ "Copyright (c) 2010 Joanmarie Diggs"
+__license__ = "LGPL"
+
+import pyatspi
+
+import orca.default as default
+import orca.orca_state as orca_state
+import orca.speech as speech
+
+from script_utilities import Utilities
+
+########################################################################
+# #
+# The DDU script class. #
+# #
+########################################################################
+
+class Script(default.Script):
+
+ def __init__(self, app):
+ """Creates a new script for the given application.
+
+ Arguments:
+ - app: the application to create a script for.
+ """
+
+ default.Script.__init__(self, app)
+ self._progressBarShowing = False
+ self._resultsLabel = None
+
+ def getUtilities(self):
+ """Returns the utilites for this script."""
+
+ return Utilities(self)
+
+ def _presentResults(self):
+ """Presents the results found by the DDU. If we present the results
+ also resets self._resultsLabel so that we don't present it twice.
+
+ Returns True if the results were presented; False otherwise.
+ """
+
+ if self._resultsLabel:
+ text = self.utilities.displayedText(self._resultsLabel)
+ if text:
+ # TODO - JD: Might be a good candidate for a flash
+ # braille message.
+ #
+ speech.speak(text)
+ self._resultsLabel = None
+ return True
+
+ return False
+
+ def stopSpeechOnActiveDescendantChanged(self, event):
+ """Whether or not speech should be stopped prior to setting the
+ locusOfFocus in onActiveDescendantChanged.
+
+ Arguments:
+ - event: the Event
+
+ Returns True if speech should be stopped; False otherwise.
+ """
+
+ # Intentionally doing an equality check for performance
+ # purposes.
+ #
+ if event.any_data == orca_state.locusOfFocus:
+ return False
+
+ return True
+
+ def onStateChanged(self, event):
+ """Called whenever an object's state changes.
+
+ Arguments:
+ - event: the Event
+ """
+
+ if event.type.startswith("object:state-changed:showing") \
+ and event.detail1 == 1 and self._progressBarShowing \
+ and event.source.getRole() == pyatspi.ROLE_FILLER:
+ self._progressBarShowing = False
+ labels = self.utilities.descendantsWithRole(
+ event.source, pyatspi.ROLE_LABEL)
+ if len(labels) == 1:
+ # Most of the time, the label has its text by the time
+ # the progress bar disappears. On occasion, we have to
+ # wait for a text inserted event. So we'll store the
+ # results label, try to present it now, and be ready
+ # to present it later.
+ #
+ self._resultsLabel = labels[0]
+ self._presentResults()
+ return
+
+ default.Script.onStateChanged(self, event)
+
+ def onTextInserted(self, event):
+ """Called whenever text is inserted into an object.
+
+ Arguments:
+ - event: the Event
+ """
+
+ if self.utilities.isSameObject(event.source, self._resultsLabel):
+ if self._presentResults():
+ return
+
+ default.Script.onTextInserted(self, event)
+
+ def onValueChanged(self, event):
+ """Called whenever an object's value changes.
+
+ Arguments:
+ - event: the Event
+ """
+
+ # When the progress bar appears, its events are initially emitted
+ # by an object of ROLE_SPLIT_PANE. Looking for it is sufficient
+ # hierarchy tickling to cause it to emit the expected events.
+ #
+ if event.source.getRole() == pyatspi.ROLE_SPLIT_PANE:
+ if self.utilities.descendantsWithRole(
+ event.source.parent, pyatspi.ROLE_PROGRESS_BAR):
+ self._progressBarShowing = True
+
+ default.Script.onValueChanged(self, event)
diff --git a/src/orca/scripts/apps/ddu/script_utilities.py b/src/orca/scripts/apps/ddu/script_utilities.py
new file mode 100644
index 0000000..3a3d9de
--- /dev/null
+++ b/src/orca/scripts/apps/ddu/script_utilities.py
@@ -0,0 +1,142 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Commonly-required utility methods needed by -- and potentially
+ customized by -- application and toolkit scripts. They have
+ been pulled out from the scripts because certain scripts had
+ gotten way too large as a result of including these methods."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__ = "LGPL"
+
+import pyatspi
+
+import orca.script_utilities as script_utilities
+
+#############################################################################
+# #
+# Utilities #
+# #
+#############################################################################
+
+class Utilities(script_utilities.Utilities):
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ script_utilities.Utilities.__init__(self, script)
+
+ #########################################################################
+ # #
+ # Utilities for finding, identifying, and comparing accessibles #
+ # #
+ #########################################################################
+
+ def _isBogusCellText(self, cell, string):
+ """Attempts to identify text in a cell which the DDU is exposing
+ to us, but which is not actually displayed to the user.
+
+ Arguments:
+ - cell: The cell we wish to examine.
+ - string: The string we're considering for that cell
+
+ Returns True if we think the string should not be presented to
+ the user.
+ """
+
+ if string.isdigit() and self.cellIndex(cell) == 2 \
+ and cell.parent.getRole() == pyatspi.ROLE_TABLE_CELL:
+ try:
+ table = cell.parent.parent.queryTable()
+ except:
+ pass
+ else:
+ index = self.cellIndex(cell.parent)
+ col = table.getColumnAtIndex(index)
+ if col == 0:
+ return True
+
+ return False
+
+ def realActiveDescendant(self, obj):
+ """Given an object that should be a child of an object that
+ manages its descendants, return the child that is the real
+ active descendant carrying useful information.
+
+ Arguments:
+ - obj: an object that should be a child of an object that
+ manages its descendants.
+ """
+
+ try:
+ return self._script.\
+ generatorCache[self.REAL_ACTIVE_DESCENDANT][obj]
+ except:
+ if not self._script.\
+ generatorCache.has_key(self.REAL_ACTIVE_DESCENDANT):
+ self._script.generatorCache[self.REAL_ACTIVE_DESCENDANT] = {}
+ activeDescendant = None
+
+ # If obj is a table cell and all of it's children are table cells
+ # (probably cell renderers), then return the first child which has
+ # a non zero length text string. If no such object is found, just
+ # fall through and use the default approach below. See bug #376791
+ # for more details.
+ #
+ if obj.getRole() == pyatspi.ROLE_TABLE_CELL and obj.childCount:
+ nonTableCellFound = False
+ for child in obj:
+ if child.getRole() != pyatspi.ROLE_TABLE_CELL:
+ nonTableCellFound = True
+ if not nonTableCellFound:
+ for child in obj:
+ string = self.substring(child, 0, -1)
+ # Here is where this method differs from the default
+ # scripts: We break once we find text, and we hack
+ # out the number in the first column.
+ #
+ if string:
+ if not self._isBogusCellText(child, string):
+ activeDescendant = child
+ break
+
+ self._script.generatorCache[self.REAL_ACTIVE_DESCENDANT][obj] = \
+ activeDescendant or obj
+ return self._script.generatorCache[self.REAL_ACTIVE_DESCENDANT][obj]
+
+ #########################################################################
+ # #
+ # Utilities for working with the accessible text interface #
+ # #
+ #########################################################################
+
+
+
+ #########################################################################
+ # #
+ # Miscellaneous Utilities #
+ # #
+ #########################################################################
diff --git a/src/orca/scripts/apps/ekiga.py b/src/orca/scripts/apps/ekiga.py
index c532999..d4e74bd 100644
--- a/src/orca/scripts/apps/ekiga.py
+++ b/src/orca/scripts/apps/ekiga.py
@@ -78,7 +78,7 @@ class Script(default.Script):
# has STATE_FOCUSABLE, but not STATE_FOCUSED. The default script
# will ignore these events as a result. See bug 574221.
#
- window = self.getTopLevel(event.source)
+ window = self.utilities.topLevelObject(event.source)
if not window or window.getRole() != pyatspi.ROLE_DIALOG:
return default.Script.onActiveDescendantChanged(self, event)
@@ -152,7 +152,8 @@ class Script(default.Script):
"""
if event.source.getRole() == pyatspi.ROLE_SPLIT_PANE:
- textObjects = self.findByRole(event.source, pyatspi.ROLE_TEXT)
+ textObjects = self.utilities.descendantsWithRole(
+ event.source, pyatspi.ROLE_TEXT)
return
default.Script.onValueChanged(self, event)
diff --git a/src/orca/scripts/apps/empathy/Makefile.am b/src/orca/scripts/apps/empathy/Makefile.am
index 11f10fa..5891ea0 100644
--- a/src/orca/scripts/apps/empathy/Makefile.am
+++ b/src/orca/scripts/apps/empathy/Makefile.am
@@ -2,7 +2,8 @@ orca_pathdir=$(pyexecdir)
orca_python_PYTHON = \
__init__.py \
- script.py
+ script.py \
+ script_utilities.py
orca_pythondir=$(pyexecdir)/orca/scripts/apps/empathy
diff --git a/src/orca/scripts/apps/empathy/script.py b/src/orca/scripts/apps/empathy/script.py
index 11e191a..b84d049 100644
--- a/src/orca/scripts/apps/empathy/script.py
+++ b/src/orca/scripts/apps/empathy/script.py
@@ -30,6 +30,8 @@ import pyatspi
import orca.chat as chat
import orca.default as default
+from script_utilities import Utilities
+
########################################################################
# #
# The Empathy script class. #
@@ -55,6 +57,11 @@ class Script(default.Script):
return chat.Chat(self, self._buddyListAncestries)
+ def getUtilities(self):
+ """Returns the utilites for this script."""
+
+ return Utilities(self)
+
def setupInputEventHandlers(self):
"""Defines InputEventHandler fields for this script that can be
called by the key and braille bindings. Here we need to add the
@@ -98,25 +105,6 @@ class Script(default.Script):
self.chat.setAppPreferences(prefs)
- def getChildNodes(self, obj):
- """Gets all of the children that have RELATION_NODE_CHILD_OF pointing
- to this expanded table cell.
-
- Arguments:
- -obj: the Accessible Object
-
- Returns: a list of all the child nodes
- """
-
- reportedNodes = default.Script.getChildNodes(self, obj)
- actualNodes = []
- for node in reportedNodes:
- child = self.getRealActiveDescendant(node)
- if child and child.name:
- actualNodes.append(child)
-
- return actualNodes
-
def onTextInserted(self, event):
"""Called whenever text is added to an object."""
@@ -137,7 +125,8 @@ class Script(default.Script):
# events we need to present text added to the chatroom are
# missing.
#
- allPageTabs = self.findByRole(event.source, pyatspi.ROLE_PAGE_TAB)
+ allPageTabs = self.utilities.descendantsWithRole(
+ event.source, pyatspi.ROLE_PAGE_TAB)
default.Script.onWindowActivated(self, event)
diff --git a/src/orca/scripts/apps/empathy/script_utilities.py b/src/orca/scripts/apps/empathy/script_utilities.py
new file mode 100644
index 0000000..077d4fa
--- /dev/null
+++ b/src/orca/scripts/apps/empathy/script_utilities.py
@@ -0,0 +1,73 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Commonly-required utility methods needed by -- and potentially
+ customized by -- application and toolkit scripts. They have
+ been pulled out from the scripts because certain scripts had
+ gotten way too large as a result of including these methods."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__ = "LGPL"
+
+import orca.script_utilities as script_utilities
+
+#############################################################################
+# #
+# Utilities #
+# #
+#############################################################################
+
+class Utilities(script_utilities.Utilities):
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ script_utilities.Utilities.__init__(self, script)
+
+ #########################################################################
+ # #
+ # Utilities for finding, identifying, and comparing accessibles #
+ # #
+ #########################################################################
+
+ def childNodes(self, obj):
+ """Gets all of the children that have RELATION_NODE_CHILD_OF pointing
+ to this expanded table cell.
+
+ Arguments:
+ -obj: the Accessible Object
+
+ Returns: a list of all the child nodes
+ """
+
+ reportedNodes = script_utilities.Utilities.childNodes(self, obj)
+ actualNodes = []
+ for node in reportedNodes:
+ child = self.realActiveDescendant(node)
+ if child and child.name:
+ actualNodes.append(child)
+
+ return actualNodes
diff --git a/src/orca/scripts/apps/evolution/Makefile.am b/src/orca/scripts/apps/evolution/Makefile.am
index ef31b58..c257d57 100644
--- a/src/orca/scripts/apps/evolution/Makefile.am
+++ b/src/orca/scripts/apps/evolution/Makefile.am
@@ -4,6 +4,7 @@ orca_python_PYTHON = \
__init__.py \
formatting.py \
script.py \
+ script_utilities.py \
speech_generator.py
orca_pythondir=$(pyexecdir)/orca/scripts/apps/evolution
diff --git a/src/orca/scripts/apps/evolution/script.py b/src/orca/scripts/apps/evolution/script.py
index 9dd1251..8d13969 100644
--- a/src/orca/scripts/apps/evolution/script.py
+++ b/src/orca/scripts/apps/evolution/script.py
@@ -41,8 +41,10 @@ import orca.settings as settings
from orca.orca_i18n import _ # for gettext support
-from speech_generator import SpeechGenerator
from formatting import Formatting
+from speech_generator import SpeechGenerator
+from script_utilities import Utilities
+
########################################################################
# #
# The Evolution script class. #
@@ -139,6 +141,11 @@ class Script(default.Script):
"""Returns the formatting strings for this script."""
return Formatting(self)
+ def getUtilities(self):
+ """Returns the utilites for this script."""
+
+ return Utilities(self)
+
def setupInputEventHandlers(self):
"""Defines InputEventHandler fields for this script that can be
called by the key and braille bindings. In this particular case,
@@ -192,46 +199,23 @@ class Script(default.Script):
return listeners
- def findUnrelatedLabels(self, root):
- """Returns a list containing all the unrelated (i.e., have no
- relations to anything and are not a fundamental element of a
- more atomic component like a combo box) labels under the given
- root. Note that the labels must also be showing on the display.
-
- Arguments:
- - root the Accessible object to traverse
-
- Returns a list of unrelated labels under the given root.
+ def isActivatableEvent(self, event):
+ """Returns True if the given event is one that should cause this
+ script to become the active script. This is only a hint to
+ the focus tracking manager and it is not guaranteed this
+ request will be honored. Note that by the time the focus
+ tracking manager calls this method, it thinks the script
+ should become active. This is an opportunity for the script
+ to say it shouldn't.
"""
- labels = default.Script.findUnrelatedLabels(self, root)
- for i, label in enumerate(labels):
- if not label.getState().contains(pyatspi.STATE_SENSITIVE):
- labels.remove(label)
- else:
- try:
- text = label.queryText()
- except:
- pass
- else:
- attr = text.getAttributes(0)
- if attr[0]:
- [charKeys, charDict] = \
- self.textAttrsToDictionary(attr[0])
- if charDict.get('weight', '400') == '700':
- if self.isWizard(root):
- # We've passed the wizard info at the top,
- # which is what we want to present. The rest
- # is noise.
- #
- return labels[0:i]
- else:
- # This label is bold and thus serving as a
- # heading. As such, it's not really unrelated.
- #
- labels.remove(label)
-
- return labels
+ # If the Evolution window is not focused, ignore this event.
+ #
+ window = self.utilities.topLevelObject(event.source)
+ if window and not window.getState().contains(pyatspi.STATE_ACTIVE):
+ return False
+
+ return True
def toggleReadMail(self, inputEvent):
""" Toggle whether we present new mail if we not not the active script.+
@@ -258,478 +242,6 @@ class Script(default.Script):
return True
- def readPageTab(self, tab):
- """Speak/Braille the given page tab. The speech verbosity is set
- to VERBOSITY_LEVEL_BRIEF for this operation and then restored
- to its previous value on completion.
-
- Arguments:
- - tab: the page tab to speak/braille.
- """
-
- brailleGen = self.brailleGenerator
- speechGen = self.speechGenerator
-
- savedSpeechVerbosityLevel = settings.speechVerbosityLevel
- settings.speechVerbosityLevel = settings.VERBOSITY_LEVEL_BRIEF
- utterances = speechGen.generateSpeech(tab)
- speech.speak(utterances)
- settings.speechVerbosityLevel = savedSpeechVerbosityLevel
-
- self.displayBrailleRegions(brailleGen.generateBraille(tab))
-
- def getTimeForCalRow(self, row, noIncs):
- """Return a string equivalent to the time of the given row in
- the calendar day view. Each calendar row is equivalent to
- a certain time interval (from 5 minutes upto 1 hour), with
- time (row 0) starting at 12 am (midnight).
-
- Arguments:
- - row: the row number.
- - noIncs: the number of equal increments that the 24 hour period
- is divided into.
-
- Returns the time as a string.
- """
-
- totalMins = timeIncrements[noIncs] * row
-
- if totalMins < 720:
- suffix = 'A.M.'
- else:
- totalMins -= 720
- suffix = 'P.M.'
-
- hrs = hours[totalMins / 60]
- mins = minutes[totalMins % 60]
-
- return hrs + ' ' + mins + ' ' + suffix
-
- def getAllSelectedText(self, obj):
- """Get all the text applicable text selections for the given object.
- If there is selected text, look to see if there are any previous
- or next text objects that also have selected text and add in their
- text contents.
-
- Arguments:
- - obj: the text object to start extracting the selected text from.
-
- Returns: all the selected text contents plus the start and end
- offsets within the text for the given object.
- """
-
- textContents = ""
- startOffset = 0
- endOffset = 0
- if obj.queryText().getNSelections() > 0:
- [textContents, startOffset, endOffset] = \
- self.getSelectedText(obj)
-
- # Unfortunately, Evolution doesn't use the FLOWS_FROM and
- # FLOWS_TO relationships to easily allow us to get to previous
- # and next text objects. Instead we have to move up the
- # component hierarchy until we get to the object containing all
- # the panels (with each line containing a single text item).
- # We can then check in both directions to see if there is other
- # contiguous text that is selected. We also have to jump over
- # zero length (empty) text lines and continue checking on the
- # other side.
- #
- container = obj.parent.parent
- current = obj.parent.getIndexInParent()
- morePossibleSelections = True
- while morePossibleSelections:
- morePossibleSelections = False
- if (current-1) >= 0:
- prevPanel = container[current-1]
- try:
- prevObj = prevPanel[0]
- displayedText = prevObj.queryText().getText(0, -1)
- if len(displayedText) == 0:
- current -= 1
- morePossibleSelections = True
- elif prevObj.queryText().getNSelections() > 0:
- [newTextContents, start, end] = \
- self.getSelectedText(prevObj)
- textContents = newTextContents + " " + textContents
- current -= 1
- morePossibleSelections = True
- except:
- pass
-
- current = obj.parent.getIndexInParent()
- morePossibleSelections = True
- while morePossibleSelections:
- morePossibleSelections = False
- if (current+1) < container.childCount:
- nextPanel = container[current+1]
- try:
- nextObj = nextPanel[0]
- displayedText = nextObj.queryText().getText(0, -1)
- if len(displayedText) == 0:
- current += 1
- morePossibleSelections = True
- elif nextObj.queryText().getNSelections() > 0:
- [newTextContents, start, end] = \
- self.getSelectedText(nextObj)
- textContents += " " + newTextContents
- current += 1
- morePossibleSelections = True
- except:
- pass
-
- return [textContents, startOffset, endOffset]
-
- def hasTextSelections(self, obj):
- """Return an indication of whether this object has selected text.
- Note that it's possible that this object has no text, but is part
- of a selected text area. Because of this, we need to check the
- objects on either side to see if they are none zero length and
- have text selections.
-
- Arguments:
- - obj: the text object to start checking for selected text.
-
- Returns: an indication of whether this object has selected text,
- or adjacent text objects have selected text.
- """
-
- currentSelected = False
- otherSelected = False
- nSelections = obj.queryText().getNSelections()
- if nSelections:
- currentSelected = True
- else:
- otherSelected = False
- displayedText = obj.queryText().getText(0, -1)
- if len(displayedText) == 0:
- container = obj.parent.parent
- current = obj.parent.getIndexInParent()
- morePossibleSelections = True
- while morePossibleSelections:
- morePossibleSelections = False
- if (current-1) >= 0:
- prevPanel = container[current-1]
- prevObj = prevPanel[0]
- try:
- prevObjText = prevObj.queryText()
- except:
- prevObjText = None
-
- if prevObj and prevObjText:
- if prevObjText.getNSelections() > 0:
- otherSelected = True
- else:
- displayedText = prevObjText.getText(0, -1)
- if len(displayedText) == 0:
- current -= 1
- morePossibleSelections = True
-
- current = obj.parent.getIndexInParent()
- morePossibleSelections = True
- while morePossibleSelections:
- morePossibleSelections = False
- if (current+1) < container.childCount:
- nextPanel = container[current+1]
- nextObj = nextPanel[0]
- try:
- nextObjText = nextObj.queryText()
- except:
- nextObjText = None
-
- if nextObj and nextObjText:
- if nextObjText.getNSelections() > 0:
- otherSelected = True
- else:
- displayedText = nextObjText.getText(0, -1)
- if len(displayedText) == 0:
- current += 1
- morePossibleSelections = True
-
- return [currentSelected, otherSelected]
-
- 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.
-
- We have to subclass this because Evolution lays out its messages
- such that each paragraph is in its own panel, each of which is
- in a higher level panel. So, we just traverse through the
- children.
-
- 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.
- """
-
- if not obj:
- return
-
- try:
- text = obj.queryText()
- except NotImplementedError:
- return
-
- panel = obj.parent
- htmlPanel = panel.parent
- startIndex = panel.getIndexInParent()
- i = startIndex
- total = htmlPanel.childCount
- textObjs = []
- startOffset = text.caretOffset
- offset = text.caretOffset
- string = ""
- done = False
-
- # Determine the correct "say all by" mode to use.
- #
- if settings.sayAllStyle == settings.SAYALL_STYLE_SENTENCE:
- mode = pyatspi.TEXT_BOUNDARY_SENTENCE_END
- elif settings.sayAllStyle == settings.SAYALL_STYLE_LINE:
- mode = pyatspi.TEXT_BOUNDARY_LINE_START
- else:
- mode = pyatspi.TEXT_BOUNDARY_LINE_START
-
- while not done:
- panel = htmlPanel.getChildAtIndex(i)
- if panel != None:
- textObj = panel.getChildAtIndex(0)
- try:
- text = textObj.queryText()
- except NotImplementedError:
- return
- textObjs.append(textObj)
- length = text.characterCount
-
- while offset <= length:
- [mystr, start, end] = text.getTextAtOffset(offset, mode)
- endOffset = end
-
- if len(mystr) != 0:
- string += " " + mystr
-
- if mode == pyatspi.TEXT_BOUNDARY_LINE_START or \
- len(mystr) == 0 or mystr[len(mystr)-1] in '.?!':
- string = self.adjustForRepeats(string)
- if string.decode("UTF-8").isupper():
- voice = settings.voices[settings.UPPERCASE_VOICE]
- else:
- voice = settings.voices[settings.DEFAULT_VOICE]
-
- if not textObjs:
- textObjs.append(textObj)
- if len(string) != 0:
- yield [speechserver.SayAllContext(textObjs, string,
- startOffset, endOffset),
- voice]
- textObjs = []
- string = ""
- startOffset = endOffset
-
- if len(mystr) == 0 or end == length:
- break
- else:
- offset = end
-
- offset = 0
- i += 1
- if i == total:
- done = True
-
- # If there is anything left unspoken, speak it now.
- #
- if len(string) != 0:
- string = self.adjustForRepeats(string)
- if string.decode("UTF-8").isupper():
- voice = settings.voices[settings.UPPERCASE_VOICE]
- else:
- voice = settings.voices[settings.DEFAULT_VOICE]
-
- yield [speechserver.SayAllContext(textObjs, string,
- startOffset, endOffset),
- voice]
-
- def __sayAllProgressCallback(self, context, callbackType):
- """Provide feedback during the sayAll operation.
- """
-
- if callbackType == speechserver.SayAllContext.PROGRESS:
- #print "PROGRESS", context.utterance, context.currentOffset
- return
- elif callbackType == speechserver.SayAllContext.INTERRUPTED:
- #print "INTERRUPTED", context.utterance, context.currentOffset
- offset = context.currentOffset
- for i in range(0, len(context.obj)):
- obj = context.obj[i]
- charCount = obj.queryText().characterCount
- if offset > charCount:
- offset -= charCount
- else:
- obj.queryText().setCaretOffset(offset)
- break
- elif callbackType == speechserver.SayAllContext.COMPLETED:
- #print "COMPLETED", context.utterance, context.currentOffset
- obj = context.obj[len(context.obj)-1]
- obj.queryText().setCaretOffset(context.currentOffset)
- orca.setLocusOfFocus(None, obj, notifyPresentationManager=False)
-
- # If there is a selection, clear it. See bug #489504 for more details.
- # This is not straight forward with Evolution. all the text is in
- # an HTML panel which contains multiple panels, each containing a
- # single text object.
- #
- panel = obj.parent
- htmlPanel = panel.parent
- for i in range(0, htmlPanel.childCount):
- panel = htmlPanel.getChildAtIndex(i)
- if panel != None:
- textObj = panel.getChildAtIndex(0)
- try:
- text = textObj.queryText()
- except:
- pass
- else:
- if text.getNSelections():
- text.removeSelection(0)
-
- def isSpellingSuggestionsList(self, obj):
- """Returns True if obj is the list of spelling suggestions
- in the spellcheck dialog.
-
- Arguments:
- - obj: the Accessible object of interest.
- """
-
- # The list of spelling suggestions is a table whose parent is
- # a scroll pane. This in and of itself is not sufficiently
- # unique. What makes the spell check dialog unique is the
- # quantity of push buttons found. If we find this combination,
- # we'll assume its the spelling dialog.
- #
- if obj and obj.getRole() == pyatspi.ROLE_TABLE_CELL:
- obj = obj.parent
-
- if not obj \
- or obj.getRole() != pyatspi.ROLE_TABLE \
- or obj.parent.getRole() != pyatspi.ROLE_SCROLL_PANE:
- return False
-
- topLevel = self.getTopLevel(obj.parent)
- if not self.isSameObject(topLevel, self.spellCheckDialog):
- # The group of buttons is found in a filler which is a
- # sibling of the scroll pane.
- #
- for sibling in obj.parent.parent:
- if sibling.getRole() == pyatspi.ROLE_FILLER:
- buttonCount = 0
- for child in sibling:
- if child.getRole() == pyatspi.ROLE_PUSH_BUTTON:
- buttonCount += 1
- if buttonCount >= 5:
- self.spellCheckDialog = topLevel
- return True
- else:
- return True
-
- return False
-
- def getMisspelledWordAndBody(self, suggestionsList, messagePanel):
- """Gets the misspelled word from the spelling dialog and the
- list of words from the message body.
-
- Arguments:
- - suggestionsList: the list of spelling suggestions from the
- spellcheck dialog
- - messagePanel: the panel containing the message being checked
- for spelling
-
- Returns [mispelledWord, messageBody]
- """
-
- misspelledWord, messageBody = "", []
-
- # Look for the "Suggestions for "xxxxx" label in the spell
- # checker dialog panel. Extract out the xxxxx. This will be
- # the misspelled word.
- #
- text = self.getDisplayedLabel(suggestionsList) or ""
- words = text.split()
- for word in words:
- if word[0] in ["'", '"']:
- misspelledWord = word[1:len(word) - 1]
- break
-
- if messagePanel != None:
- allTextObjects = self.findByRole(messagePanel, pyatspi.ROLE_TEXT)
- for obj in allTextObjects:
- for word in self.getText(obj, 0, -1).split():
- messageBody.append(word)
-
- return [misspelledWord, messageBody]
-
- def isMessageBodyText(self, obj):
- """Returns True if obj is in the body of an email message.
-
- Arguments:
- - obj: the Accessible object of interest.
- """
-
- try:
- obj.queryHypertext()
- ancestor = obj.parent.parent
- except:
- return False
- else:
- # The accessible text objects in the header at the top
- # of the message also have STATE_MULTI_LINE. But they
- # are inside panels which are inside table cells; the
- # body text is not. See bug #567428.
- #
- return (obj.getState().contains(pyatspi.STATE_MULTI_LINE) \
- and ancestor.getRole() != pyatspi.ROLE_TABLE_CELL)
-
- def presentMessageLine(self, obj, newLocusOfFocus):
- """Speak/braille the line at the current text caret offset.
- """
-
- [string, caretOffset, startOffset] = self.getTextLineAtCaret(obj)
- self.updateBraille(newLocusOfFocus)
- if settings.enableSpeechIndentation:
- self.speakTextIndentation(obj, string)
- line = self.adjustForRepeats(string)
-
- if self.speakBlankLine(obj):
- # Translators: "blank" is a short word to mean the
- # user has navigated to an empty line.
- #
- speech.speak(_("blank"), None, False)
- else:
- speech.speak(line, None, False)
-
- def sayAll(self, inputEvent):
- """Speak all the text associated with the text object that has
- focus. We have to define our own method here because Evolution
- does not implement the FLOWS_TO relationship and all the text
- are in an HTML panel which contains multiple panels, each
- containing a single text object.
-
- Arguments:
- - inputEvent: if not None, the input event that caused this action.
- """
-
- debug.println(self.debugLevel, "evolution.sayAll.")
- try:
- if orca_state.locusOfFocus and orca_state.locusOfFocus.queryText():
- speech.sayAll(self.textLines(orca_state.locusOfFocus),
- self.__sayAllProgressCallback)
- except:
- default.Script.sayAll(self, inputEvent)
-
- return True
-
# This method tries to detect and handle the following cases:
# 1) Mail view: current message pane: individual lines of text.
# 2) Mail view: current message pane: "standard" mail header lines.
@@ -742,6 +254,8 @@ class Script(default.Script):
# 9) Spell Checking Dialog
# 10) Mail view: message area - attachments.
+ # [[[TODO - JD: This method is way, way too huge]]]
+ #
def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
"""Called when the visual object with focus changes.
@@ -787,7 +301,7 @@ class Script(default.Script):
# "text", "panel" and "unknown". If we find that, then (hopefully)
# it's a line in the mail message and we get the utterances to
# speak for that Text.
- if self.isMessageBodyText(event.source) \
+ if self.utilities.isMessageBody(event.source) \
and not event.source.getState().contains(pyatspi.STATE_EDITABLE):
debug.println(self.debugLevel,
"evolution.locusOfFocusChanged - mail view: " \
@@ -821,7 +335,8 @@ class Script(default.Script):
pyatspi.ROLE_PANEL, \
pyatspi.ROLE_TABLE_CELL]
if settings.readTableCellRow \
- and (self.isDesiredFocusedItem(event.source, self.rolesList)):
+ and (self.utilities.hasMatchingHierarchy(
+ event.source, self.rolesList)):
debug.println(self.debugLevel,
"evolution.locusOfFocusChanged - mail view: " \
+ "current message pane: " \
@@ -831,7 +346,7 @@ class Script(default.Script):
parent = obj.parent
if parent.getRole() == pyatspi.ROLE_TABLE:
parentTable = parent.queryTable()
- index = self.getCellIndex(obj)
+ index = self.utilities.cellIndex(obj)
row = parentTable.getRowAtIndex(index)
utterances = []
regions = []
@@ -872,8 +387,8 @@ class Script(default.Script):
self.rolesList = [pyatspi.ROLE_TABLE_CELL, \
pyatspi.ROLE_TREE_TABLE]
- if settings.readTableCellRow \
- and (self.isDesiredFocusedItem(event.source, self.rolesList)):
+ if settings.readTableCellRow and self.utilities.hasMatchingHierarchy(
+ event.source, self.rolesList):
debug.println(self.debugLevel,
"evolution.locusOfFocusChanged - mail view: " \
+ "message header list.")
@@ -888,7 +403,7 @@ class Script(default.Script):
parent = event.source.parent
parentTable = parent.queryTable()
- index = self.getCellIndex(event.source)
+ index = self.utilities.cellIndex(event.source)
row = parentTable.getRowAtIndex(index)
column = parentTable.getColumnAtIndex(index)
@@ -1136,7 +651,7 @@ class Script(default.Script):
self.rolesList = [rolenames.ROLE_CALENDAR_EVENT, \
rolenames.ROLE_CALENDAR_VIEW]
- if self.isDesiredFocusedItem(event.source, self.rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, self.rolesList):
debug.println(self.debugLevel,
"evolution.locusOfFocusChanged - calendar view: " \
+ "day view: tabbing to day with appts.")
@@ -1170,13 +685,14 @@ class Script(default.Script):
speech.speak(utterances)
startTime = 'Start time ' + \
- self.getTimeForCalRow(j, noRows)
+ self.utilities.timeForCalRow(j, noRows)
brailleRegions.append(braille.Region(startTime))
speech.speak(startTime)
apptLen = apptExtents.height / extents.height
endTime = 'End time ' + \
- self.getTimeForCalRow(j + apptLen, noRows)
+ self.utilities.timeForCalRow(j + apptLen,
+ noRows)
brailleRegions.append(braille.Region(endTime))
speech.speak(endTime)
self.displayBrailleRegions([brailleRegions,
@@ -1206,7 +722,7 @@ class Script(default.Script):
self.rolesList = [pyatspi.ROLE_UNKNOWN, \
pyatspi.ROLE_TABLE, \
rolenames.ROLE_CALENDAR_VIEW]
- if self.isDesiredFocusedItem(event.source, self.rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, self.rolesList):
debug.println(self.debugLevel,
"evolution.locusOfFocusChanged - calendar view: " \
+ "day view: moving with arrow keys.")
@@ -1234,13 +750,14 @@ class Script(default.Script):
speech.speak(utterances)
startTime = 'Start time ' + \
- self.getTimeForCalRow(index, noRows)
+ self.utilities.timeForCalRow(index, noRows)
brailleRegions.append(braille.Region(startTime))
speech.speak(startTime)
apptLen = apptExtents.height / extents.height
endTime = 'End time ' + \
- self.getTimeForCalRow(index + apptLen, noRows)
+ self.utilities.timeForCalRow(index + apptLen,
+ noRows)
brailleRegions.append(braille.Region(endTime))
speech.speak(endTime)
self.displayBrailleRegions([brailleRegions,
@@ -1248,7 +765,8 @@ class Script(default.Script):
found = True
if not found:
- startTime = 'Start time ' + self.getTimeForCalRow(index, noRows)
+ startTime = 'Start time ' + \
+ self.utilities.timeForCalRow(index, noRows)
brailleRegions.append(braille.Region(startTime))
speech.speak(startTime)
@@ -1288,7 +806,7 @@ class Script(default.Script):
pyatspi.ROLE_TABLE, \
pyatspi.ROLE_UNKNOWN, \
pyatspi.ROLE_SCROLL_PANE]
- if self.isDesiredFocusedItem(event.source, self.rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, self.rolesList):
debug.println(self.debugLevel,
"evolution.locusOfFocusChanged - preferences dialog: " \
+ "table cell in options list.")
@@ -1322,7 +840,7 @@ class Script(default.Script):
pyatspi.ROLE_FILLER, \
pyatspi.ROLE_FILLER, \
pyatspi.ROLE_DIALOG]
- if self.isDesiredFocusedItem(event.source, self.rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, self.rolesList):
debug.println(self.debugLevel,
"evolution.locusOfFocusChanged - mail insert " \
+ "attachment dialog: unlabelled button.")
@@ -1349,7 +867,7 @@ class Script(default.Script):
#
# Note that this drops through to then use the default event
# processing in the parent class for this "focus:" event.
- if self.isMessageBodyText(event.source) \
+ if self.utilities.isMessageBody(event.source) \
and event.source.getState().contains(pyatspi.STATE_EDITABLE):
debug.println(self.debugLevel,
"evolution.locusOfFocusChanged - mail " \
@@ -1364,10 +882,10 @@ class Script(default.Script):
# ignore it. See bug #490317 for more details.
#
if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
- if self.isSameObject(event.source.parent,
+ if self.utilities.isSameObject(event.source.parent,
orca_state.locusOfFocus.parent):
lastKey = orca_state.lastNonModifierKeyEvent.event_string
- if self.isMessageBodyText(orca_state.locusOfFocus) \
+ if self.utilities.isMessageBody(orca_state.locusOfFocus) \
and lastKey not in ["Left", "Right", "Up", "Down",
"Home", "End", "Return", "Tab"]:
return
@@ -1395,8 +913,8 @@ class Script(default.Script):
# caret currently is, and use this to speak a selection of the
# surrounding text, to give the user context for the current misspelt
# word.
- if self.isSpellingSuggestionsList(event.source) \
- or self.isSpellingSuggestionsList(newLocusOfFocus):
+ if self.utilities.isSpellingSuggestionsList(event.source) \
+ or self.utilities.isSpellingSuggestionsList(newLocusOfFocus):
debug.println(self.debugLevel,
"evolution.locusOfFocusChanged - spell checking dialog.")
@@ -1406,8 +924,8 @@ class Script(default.Script):
if not self.pointOfReference.get('activeDescendantInfo'):
[badWord, allTokens] = \
- self.getMisspelledWordAndBody(event.source,
- self.message_panel)
+ self.utilities.misspelledWordAndBody(event.source,
+ self.message_panel)
self.speakMisspeltWord(allTokens, badWord)
# 10) Mail view: message area - attachments.
@@ -1431,7 +949,7 @@ class Script(default.Script):
pyatspi.ROLE_TABLE_CELL, \
pyatspi.ROLE_TABLE, \
pyatspi.ROLE_PANEL]
- if self.isDesiredFocusedItem(event.source, self.rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, self.rolesList):
debug.println(self.debugLevel,
"evolution.locusOfFocusChanged - " \
+ "mail message area attachments.")
@@ -1444,8 +962,9 @@ class Script(default.Script):
tmp = event.source.parent.parent
table = tmp.parent.parent.parent
cell = table[table.childCount-1]
- allText = self.findByRole(cell, pyatspi.ROLE_TEXT)
- utterance = "for " + self.getText(allText[0], 0, -1)
+ allText = self.utilities.descendantsWithRole(
+ cell, pyatspi.ROLE_TEXT)
+ utterance = "for " + self.utilities.substring(allText[0], 0, -1)
speech.speak(utterance)
return
@@ -1458,157 +977,51 @@ class Script(default.Script):
default.Script.locusOfFocusChanged(self, event,
oldLocusOfFocus, newLocusOfFocus)
- def speakBlankLine(self, obj):
- """Returns True if a blank line should be spoken.
- Otherwise, returns False.
- """
-
- # Get the the AccessibleText interrface.
- try:
- text = obj.queryText()
- except NotImplementedError:
- return False
-
- # Get the line containing the caret
- caretOffset = text.caretOffset
- line = text.getTextAtOffset(caretOffset, \
- pyatspi.TEXT_BOUNDARY_LINE_START)
-
- debug.println(debug.LEVEL_FINEST,
- "speakBlankLine: start=%d, end=%d, line=<%s>" % \
- (line[1], line[2], line[0]))
-
- # If this is a blank line, announce it if the user requested
- # that blank lines be spoken.
- if line[1] == 0 and line[2] == 0:
- return settings.speakBlankLines
- else:
- return False
-
- def onStateChanged(self, event):
- """Called whenever an object's state changes. We are only
- interested in "object:state-changed:showing" events for any
- object in the Setup Assistant.
+ def stopSpeechOnActiveDescendantChanged(self, event):
+ """Whether or not speech should be stopped prior to setting the
+ locusOfFocus in onActiveDescendantChanged.
Arguments:
- event: the Event
- """
-
- if self.isWizardNewInfoEvent(event):
- if event.source.getRole() == pyatspi.ROLE_PANEL:
- self.lastSetupPanel = event.source
- self.presentWizardNewInfo(self.getTopLevel(event.source))
- return
-
- # For everything else, pass the event onto the parent class
- # to be handled in the default way.
- #
- default.Script.onStateChanged(self, event)
-
- def presentWizardNewInfo(self, obj):
- """Causes the new information displayed in a wizard to be presented
- to the user.
- Arguments:
- - obj: the Accessible object
+ Returns True if speech should be stopped; False otherwise.
"""
- if not obj:
- return
-
- # TODO - JD: Presenting the Setup Assistant (or any Wizard) as a
- # dialog means that we will repeat the dialog's name for each new
- # "screen". We should consider a 'ROLE_WIZARD' or some other means
- # for presenting these objects.
- #
- utterances = \
- self.speechGenerator.generateSpeech(obj, role=pyatspi.ROLE_DIALOG)
-
- # The following falls under the heading of "suck it and see." The
- # worst case scenario is that we present the push button and then
- # process a focus:/object:state-changed:focused event and present
- # it.
- #
- if orca_state.locusOfFocus \
- and orca_state.locusOfFocus.getRole() == pyatspi.ROLE_PUSH_BUTTON \
- and orca_state.locusOfFocus.getState().\
- contains(pyatspi.STATE_FOCUSED):
- utterances.append(
- self.speechGenerator.generateSpeech(orca_state.locusOfFocus))
+ return False
- speech.speak(utterances)
+ ########################################################################
+ # #
+ # AT-SPI OBJECT EVENT HANDLERS #
+ # #
+ ########################################################################
- def isWizard(self, obj):
- """Returns True if this object is, or is within, a wizard.
+ def onActiveDescendantChanged(self, event):
+ """Called when an object who manages its own descendants detects a
+ change in one of its children.
Arguments:
- - obj: the Accessible object
+ - event: the Event
"""
- # The Setup Assistant is a frame whose child is a panel. That panel
- # holds a bunch of other panels, one for each stage in the wizard.
- # Only the active stage's panel has STATE_SHOWING. There is also
- # one child of ROLE_FILLER which holds the buttons.
+ # The default script's onActiveDescendantChanged method is cutting
+ # off speech with a speech.stop. If we're in the spellcheck dialog,
+ # this interrupts the presentation of the context.
#
- window = self.getTopLevel(obj) or obj
- if window and window.getRole() == pyatspi.ROLE_FRAME \
- and window.childCount and window[0].getRole() == pyatspi.ROLE_PANEL:
- allPanels = panelsNotShowing = 0
- for child in window[0]:
- if child.getRole() == pyatspi.ROLE_PANEL:
- allPanels += 1
- if not child.getState().contains(pyatspi.STATE_SHOWING):
- panelsNotShowing += 1
- if allPanels - panelsNotShowing == 1 \
- and window[0].childCount - allPanels == 1:
- return True
-
- return False
-
- def isWizardNewInfoEvent(self, event):
- """Returns True if the event is judged to be the presentation of
- new information in a wizard. This method should be subclassed by
- application scripts as needed.
-
- Arguments:
- - event: the Accessible event being examined
- """
-
- if event.source.getRole() == pyatspi.ROLE_FRAME \
- and (event.type.startswith("window:activate") \
- or (event.type.startswith("object:state-changed:active") \
- and event.detail1 == 1)):
- return self.isWizard(event.source)
-
- elif event.source.getRole() == pyatspi.ROLE_PANEL \
- and event.type.startswith("object:state-changed:showing") \
- and event.detail1 == 1 \
- and not self.isSameObject(event.source, self.lastSetupPanel):
- rolesList = [pyatspi.ROLE_PANEL,
- pyatspi.ROLE_PANEL,
- pyatspi.ROLE_FRAME]
- if self.isDesiredFocusedItem(event.source, rolesList):
- return self.isWizard(event.source)
-
- return False
-
- def isActivatableEvent(self, event):
- """Returns True if the given event is one that should cause this
- script to become the active script. This is only a hint to
- the focus tracking manager and it is not guaranteed this
- request will be honored. Note that by the time the focus
- tracking manager calls this method, it thinks the script
- should become active. This is an opportunity for the script
- to say it shouldn't.
- """
+ if self.utilities.isSpellingSuggestionsList(event.source):
+ orca.setLocusOfFocus(event, event.any_data)
- # If the Evolution window is not focused, ignore this event.
- #
- window = self.getTopLevel(event.source)
- if window and not window.getState().contains(pyatspi.STATE_ACTIVE):
- return False
+ # We'll tuck away the activeDescendant information for future
+ # reference since the AT-SPI gives us little help in finding
+ # this.
+ #
+ if orca_state.locusOfFocus \
+ and (orca_state.locusOfFocus != event.source):
+ self.pointOfReference['activeDescendantInfo'] = \
+ [orca_state.locusOfFocus.parent,
+ orca_state.locusOfFocus.getIndexInParent()]
+ return
- return True
+ default.Script.onActiveDescendantChanged(self, event)
def onFocus(self, event):
"""Called whenever an object gets focus.
@@ -1628,18 +1041,20 @@ class Script(default.Script):
and orca_state.lastNonModifierKeyEvent:
string = orca_state.lastNonModifierKeyEvent.event_string
if string == "Delete":
- rolesList = [pyatspi.ROLE_TABLE_CELL, \
- pyatspi.ROLE_TREE_TABLE, \
- pyatspi.ROLE_UNKNOWN, \
+ rolesList = [pyatspi.ROLE_TABLE_CELL,
+ pyatspi.ROLE_TREE_TABLE,
+ pyatspi.ROLE_UNKNOWN,
pyatspi.ROLE_SCROLL_PANE]
oldLocusOfFocus = orca_state.locusOfFocus
- if self.isDesiredFocusedItem(event.source, rolesList) and \
- self.isDesiredFocusedItem(oldLocusOfFocus, rolesList):
+ if self.utilities.hasMatchingHierarchy(
+ event.source, rolesList) \
+ and self.utilities.hasMatchingHierarchy(
+ oldLocusOfFocus, rolesList):
parent = event.source.parent
parentTable = parent.queryTable()
- newIndex = self.getCellIndex(event.source)
+ newIndex = self.utilities.cellIndex(event.source)
newRow = parentTable.getRowAtIndex(newIndex)
- oldIndex = self.getCellIndex(oldLocusOfFocus)
+ oldIndex = self.utilities.cellIndex(oldLocusOfFocus)
oldRow = parentTable.getRowAtIndex(oldIndex)
nRows = parentTable.nRows
if (newRow != oldRow) and (oldRow != nRows):
@@ -1650,33 +1065,26 @@ class Script(default.Script):
#
default.Script.onFocus(self, event)
- def onActiveDescendantChanged(self, event):
- """Called when an object who manages its own descendants detects a
- change in one of its children.
+ def onStateChanged(self, event):
+ """Called whenever an object's state changes. We are only
+ interested in "object:state-changed:showing" events for any
+ object in the Setup Assistant.
Arguments:
- event: the Event
"""
- # The default script's onActiveDescendantChanged method is cutting
- # off speech with a speech.stop. If we're in the spellcheck dialog,
- # this interrupts the presentation of the context.
- #
- if self.isSpellingSuggestionsList(event.source):
- orca.setLocusOfFocus(event, event.any_data)
-
- # We'll tuck away the activeDescendant information for future
- # reference since the AT-SPI gives us little help in finding
- # this.
- #
- if orca_state.locusOfFocus \
- and (orca_state.locusOfFocus != event.source):
- self.pointOfReference['activeDescendantInfo'] = \
- [orca_state.locusOfFocus.parent,
- orca_state.locusOfFocus.getIndexInParent()]
+ if self.utilities.isWizardNewInfoEvent(event):
+ if event.source.getRole() == pyatspi.ROLE_PANEL:
+ self.lastSetupPanel = event.source
+ self.presentWizardNewInfo(
+ self.utilities.topLevelObject(event.source))
return
- default.Script.onActiveDescendantChanged(self, event)
+ # For everything else, pass the event onto the parent class
+ # to be handled in the default way.
+ #
+ default.Script.onStateChanged(self, event)
def onTextInserted(self, event):
"""Called whenever text is inserted into an object.
@@ -1698,10 +1106,10 @@ class Script(default.Script):
for relation in relations:
if relation.getRelationType() == pyatspi.RELATION_LABEL_FOR:
target = relation.getTarget(0)
- if self.isSpellingSuggestionsList(target):
+ if self.utilities.isSpellingSuggestionsList(target):
[badWord, allTokens] = \
- self.getMisspelledWordAndBody(target,
- self.message_panel)
+ self.utilities.misspelledWordAndBody(
+ target, self.message_panel)
self.speakMisspeltWord(allTokens, badWord)
try:
@@ -1718,27 +1126,247 @@ class Script(default.Script):
default.Script.onTextInserted(self, event)
-# Values used to construct a time string for calendar appointments.
-#
-timeIncrements = {}
-timeIncrements[288] = 5
-timeIncrements[144] = 10
-timeIncrements[96] = 15
-timeIncrements[48] = 30
-timeIncrements[24] = 60
-
-minutes = {}
-minutes[0] = ''
-minutes[5] = '5'
-minutes[10] = '10'
-minutes[15] = '15'
-minutes[20] = '20'
-minutes[25] = '25'
-minutes[30] = '30'
-minutes[35] = '35'
-minutes[40] = '40'
-minutes[45] = '45'
-minutes[50] = '50'
-minutes[55] = '55'
-
-hours = ['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']
+ ########################################################################
+ # #
+ # Methods for presenting content #
+ # #
+ ########################################################################
+
+ def presentMessageLine(self, obj, newLocusOfFocus):
+ """Speak/braille the line at the current text caret offset.
+ """
+
+ [string, caretOffset, startOffset] = self.getTextLineAtCaret(obj)
+ self.updateBraille(newLocusOfFocus)
+ if settings.enableSpeechIndentation:
+ self.speakTextIndentation(obj, string)
+ line = self.utilities.adjustForRepeats(string)
+
+ if self.utilities.speakBlankLine(obj):
+ # Translators: "blank" is a short word to mean the
+ # user has navigated to an empty line.
+ #
+ speech.speak(_("blank"), None, False)
+ else:
+ speech.speak(line, None, False)
+
+ def presentWizardNewInfo(self, obj):
+ """Causes the new information displayed in a wizard to be presented
+ to the user.
+
+ Arguments:
+ - obj: the Accessible object
+ """
+
+ if not obj:
+ return
+
+ # TODO - JD: Presenting the Setup Assistant (or any Wizard) as a
+ # dialog means that we will repeat the dialog's name for each new
+ # "screen". We should consider a 'ROLE_WIZARD' or some other means
+ # for presenting these objects.
+ #
+ utterances = \
+ self.speechGenerator.generateSpeech(obj, role=pyatspi.ROLE_DIALOG)
+
+ # The following falls under the heading of "suck it and see." The
+ # worst case scenario is that we present the push button and then
+ # process a focus:/object:state-changed:focused event and present
+ # it.
+ #
+ if orca_state.locusOfFocus \
+ and orca_state.locusOfFocus.getRole() == pyatspi.ROLE_PUSH_BUTTON \
+ and orca_state.locusOfFocus.getState().\
+ contains(pyatspi.STATE_FOCUSED):
+ utterances.append(
+ self.speechGenerator.generateSpeech(orca_state.locusOfFocus))
+
+ speech.speak(utterances)
+
+ def readPageTab(self, tab):
+ """Speak/Braille the given page tab. The speech verbosity is set
+ to VERBOSITY_LEVEL_BRIEF for this operation and then restored
+ to its previous value on completion.
+
+ Arguments:
+ - tab: the page tab to speak/braille.
+ """
+
+ brailleGen = self.brailleGenerator
+ speechGen = self.speechGenerator
+
+ savedSpeechVerbosityLevel = settings.speechVerbosityLevel
+ settings.speechVerbosityLevel = settings.VERBOSITY_LEVEL_BRIEF
+ utterances = speechGen.generateSpeech(tab)
+ speech.speak(utterances)
+ settings.speechVerbosityLevel = savedSpeechVerbosityLevel
+
+ self.displayBrailleRegions(brailleGen.generateBraille(tab))
+
+ 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.
+
+ We have to subclass this because Evolution lays out its messages
+ such that each paragraph is in its own panel, each of which is
+ in a higher level panel. So, we just traverse through the
+ children.
+
+ 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.
+ """
+
+ if not obj:
+ return
+
+ try:
+ text = obj.queryText()
+ except NotImplementedError:
+ return
+
+ panel = obj.parent
+ htmlPanel = panel.parent
+ startIndex = panel.getIndexInParent()
+ i = startIndex
+ total = htmlPanel.childCount
+ textObjs = []
+ startOffset = text.caretOffset
+ offset = text.caretOffset
+ string = ""
+ done = False
+
+ # Determine the correct "say all by" mode to use.
+ #
+ if settings.sayAllStyle == settings.SAYALL_STYLE_SENTENCE:
+ mode = pyatspi.TEXT_BOUNDARY_SENTENCE_END
+ elif settings.sayAllStyle == settings.SAYALL_STYLE_LINE:
+ mode = pyatspi.TEXT_BOUNDARY_LINE_START
+ else:
+ mode = pyatspi.TEXT_BOUNDARY_LINE_START
+
+ while not done:
+ panel = htmlPanel.getChildAtIndex(i)
+ if panel != None:
+ textObj = panel.getChildAtIndex(0)
+ try:
+ text = textObj.queryText()
+ except NotImplementedError:
+ return
+ textObjs.append(textObj)
+ length = text.characterCount
+
+ while offset <= length:
+ [mystr, start, end] = text.getTextAtOffset(offset, mode)
+ endOffset = end
+
+ if len(mystr) != 0:
+ string += " " + mystr
+
+ if mode == pyatspi.TEXT_BOUNDARY_LINE_START or \
+ len(mystr) == 0 or mystr[len(mystr)-1] in '.?!':
+ string = self.utilities.adjustForRepeats(string)
+ if string.decode("UTF-8").isupper():
+ voice = settings.voices[settings.UPPERCASE_VOICE]
+ else:
+ voice = settings.voices[settings.DEFAULT_VOICE]
+
+ if not textObjs:
+ textObjs.append(textObj)
+ if len(string) != 0:
+ yield [speechserver.SayAllContext(textObjs, string,
+ startOffset, endOffset),
+ voice]
+ textObjs = []
+ string = ""
+ startOffset = endOffset
+
+ if len(mystr) == 0 or end == length:
+ break
+ else:
+ offset = end
+
+ offset = 0
+ i += 1
+ if i == total:
+ done = True
+
+ # If there is anything left unspoken, speak it now.
+ #
+ if len(string) != 0:
+ string = self.utilities.adjustForRepeats(string)
+ if string.decode("UTF-8").isupper():
+ voice = settings.voices[settings.UPPERCASE_VOICE]
+ else:
+ voice = settings.voices[settings.DEFAULT_VOICE]
+
+ yield [speechserver.SayAllContext(textObjs, string,
+ startOffset, endOffset),
+ voice]
+
+ def __sayAllProgressCallback(self, context, callbackType):
+ """Provide feedback during the sayAll operation.
+ """
+
+ if callbackType == speechserver.SayAllContext.PROGRESS:
+ #print "PROGRESS", context.utterance, context.currentOffset
+ return
+ elif callbackType == speechserver.SayAllContext.INTERRUPTED:
+ #print "INTERRUPTED", context.utterance, context.currentOffset
+ offset = context.currentOffset
+ for i in range(0, len(context.obj)):
+ obj = context.obj[i]
+ charCount = obj.queryText().characterCount
+ if offset > charCount:
+ offset -= charCount
+ else:
+ obj.queryText().setCaretOffset(offset)
+ break
+ elif callbackType == speechserver.SayAllContext.COMPLETED:
+ #print "COMPLETED", context.utterance, context.currentOffset
+ obj = context.obj[len(context.obj)-1]
+ obj.queryText().setCaretOffset(context.currentOffset)
+ orca.setLocusOfFocus(None, obj, notifyPresentationManager=False)
+
+ # If there is a selection, clear it. See bug #489504 for more details.
+ # This is not straight forward with Evolution. all the text is in
+ # an HTML panel which contains multiple panels, each containing a
+ # single text object.
+ #
+ panel = obj.parent
+ htmlPanel = panel.parent
+ for i in range(0, htmlPanel.childCount):
+ panel = htmlPanel.getChildAtIndex(i)
+ if panel != None:
+ textObj = panel.getChildAtIndex(0)
+ try:
+ text = textObj.queryText()
+ except:
+ pass
+ else:
+ if text.getNSelections():
+ text.removeSelection(0)
+
+ def sayAll(self, inputEvent):
+ """Speak all the text associated with the text object that has
+ focus. We have to define our own method here because Evolution
+ does not implement the FLOWS_TO relationship and all the text
+ are in an HTML panel which contains multiple panels, each
+ containing a single text object.
+
+ Arguments:
+ - inputEvent: if not None, the input event that caused this action.
+ """
+
+ debug.println(self.debugLevel, "evolution.sayAll.")
+ try:
+ if orca_state.locusOfFocus and orca_state.locusOfFocus.queryText():
+ speech.sayAll(self.textLines(orca_state.locusOfFocus),
+ self.__sayAllProgressCallback)
+ except:
+ default.Script.sayAll(self, inputEvent)
+
+ return True
diff --git a/src/orca/scripts/apps/evolution/script_utilities.py b/src/orca/scripts/apps/evolution/script_utilities.py
new file mode 100644
index 0000000..061c179
--- /dev/null
+++ b/src/orca/scripts/apps/evolution/script_utilities.py
@@ -0,0 +1,487 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Commonly-required utility methods needed by -- and potentially
+ customized by -- application and toolkit scripts. They have
+ been pulled out from the scripts because certain scripts had
+ gotten way too large as a result of including these methods."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__ = "LGPL"
+
+import pyatspi
+
+import orca.debug as debug
+import orca.script_utilities as script_utilities
+import orca.settings as settings
+
+#############################################################################
+# #
+# Utilities #
+# #
+#############################################################################
+
+class Utilities(script_utilities.Utilities):
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ script_utilities.Utilities.__init__(self, script)
+
+ #########################################################################
+ # #
+ # Utilities for finding, identifying, and comparing accessibles #
+ # #
+ #########################################################################
+
+ def isMessageBody(self, obj):
+ """Returns True if obj is in the body of an email message.
+
+ Arguments:
+ - obj: the Accessible object of interest.
+ """
+
+ try:
+ obj.queryHypertext()
+ ancestor = obj.parent.parent
+ except:
+ return False
+ else:
+ # The accessible text objects in the header at the top
+ # of the message also have STATE_MULTI_LINE. But they
+ # are inside panels which are inside table cells; the
+ # body text is not. See bug #567428.
+ #
+ return (obj.getState().contains(pyatspi.STATE_MULTI_LINE) \
+ and ancestor.getRole() != pyatspi.ROLE_TABLE_CELL)
+
+ def isSpellingSuggestionsList(self, obj):
+ """Returns True if obj is the list of spelling suggestions
+ in the spellcheck dialog.
+
+ Arguments:
+ - obj: the Accessible object of interest.
+ """
+
+ # The list of spelling suggestions is a table whose parent is
+ # a scroll pane. This in and of itself is not sufficiently
+ # unique. What makes the spell check dialog unique is the
+ # quantity of push buttons found. If we find this combination,
+ # we'll assume its the spelling dialog.
+ #
+ if obj and obj.getRole() == pyatspi.ROLE_TABLE_CELL:
+ obj = obj.parent
+
+ if not obj \
+ or obj.getRole() != pyatspi.ROLE_TABLE \
+ or obj.parent.getRole() != pyatspi.ROLE_SCROLL_PANE:
+ return False
+
+ topLevel = self.topLevelObject(obj.parent)
+ if not self.isSameObject(topLevel, self._script.spellCheckDialog):
+ # The group of buttons is found in a filler which is a
+ # sibling of the scroll pane.
+ #
+ for sibling in obj.parent.parent:
+ if sibling.getRole() == pyatspi.ROLE_FILLER:
+ buttonCount = 0
+ for child in sibling:
+ if child.getRole() == pyatspi.ROLE_PUSH_BUTTON:
+ buttonCount += 1
+ if buttonCount >= 5:
+ self._script.spellCheckDialog = topLevel
+ return True
+ else:
+ return True
+
+ return False
+
+ def isWizard(self, obj):
+ """Returns True if this object is, or is within, a wizard.
+
+ Arguments:
+ - obj: the Accessible object
+ """
+
+ # The Setup Assistant is a frame whose child is a panel. That panel
+ # holds a bunch of other panels, one for each stage in the wizard.
+ # Only the active stage's panel has STATE_SHOWING. There is also
+ # one child of ROLE_FILLER which holds the buttons.
+ #
+ window = self.topLevelObject(obj) or obj
+ if window and window.getRole() == pyatspi.ROLE_FRAME \
+ and window.childCount and window[0].getRole() == pyatspi.ROLE_PANEL:
+ allPanels = panelsNotShowing = 0
+ for child in window[0]:
+ if child.getRole() == pyatspi.ROLE_PANEL:
+ allPanels += 1
+ if not child.getState().contains(pyatspi.STATE_SHOWING):
+ panelsNotShowing += 1
+ if allPanels - panelsNotShowing == 1 \
+ and window[0].childCount - allPanels == 1:
+ return True
+
+ return False
+
+ def isWizardNewInfoEvent(self, event):
+ """Returns True if the event is judged to be the presentation of
+ new information in a wizard. This method should be subclassed by
+ application scripts as needed.
+
+ Arguments:
+ - event: the Accessible event being examined
+ """
+
+ if event.source.getRole() == pyatspi.ROLE_FRAME \
+ and (event.type.startswith("window:activate") \
+ or (event.type.startswith("object:state-changed:active") \
+ and event.detail1 == 1)):
+ return self.isWizard(event.source)
+
+ elif event.source.getRole() == pyatspi.ROLE_PANEL \
+ and event.type.startswith("object:state-changed:showing") \
+ and event.detail1 == 1 \
+ and not self.isSameObject(event.source,
+ self._script.lastSetupPanel):
+ rolesList = [pyatspi.ROLE_PANEL,
+ pyatspi.ROLE_PANEL,
+ pyatspi.ROLE_FRAME]
+ if self.hasMatchingHierarchy(event.source, rolesList):
+ return self.isWizard(event.source)
+
+ return False
+
+ def unrelatedLabels(self, root):
+ """Returns a list containing all the unrelated (i.e., have no
+ relations to anything and are not a fundamental element of a
+ more atomic component like a combo box) labels under the given
+ root. Note that the labels must also be showing on the display.
+
+ Arguments:
+ - root the Accessible object to traverse
+
+ Returns a list of unrelated labels under the given root.
+ """
+
+ labels = script_utilities.Utilities.unrelatedLabels(self, root)
+ for i, label in enumerate(labels):
+ if not label.getState().contains(pyatspi.STATE_SENSITIVE):
+ labels.remove(label)
+ else:
+ try:
+ text = label.queryText()
+ except:
+ pass
+ else:
+ attr = text.getAttributes(0)
+ if attr[0]:
+ [charKeys, charDict] = \
+ self.stringToKeysAndDict(attr[0])
+ if charDict.get('weight', '400') == '700':
+ if self.isWizard(root):
+ # We've passed the wizard info at the top,
+ # which is what we want to present. The rest
+ # is noise.
+ #
+ return labels[0:i]
+ else:
+ # This label is bold and thus serving as a
+ # heading. As such, it's not really unrelated.
+ #
+ labels.remove(label)
+
+ return labels
+
+ #########################################################################
+ # #
+ # Utilities for working with the accessible text interface #
+ # #
+ #########################################################################
+
+ def allSelectedText(self, obj):
+ """Get all the text applicable text selections for the given object.
+ If there is selected text, look to see if there are any previous
+ or next text objects that also have selected text and add in their
+ text contents.
+
+ Arguments:
+ - obj: the text object to start extracting the selected text from.
+
+ Returns: all the selected text contents plus the start and end
+ offsets within the text for the given object.
+ """
+
+ if not obj or not obj.parent:
+ return ["", 0, 0]
+
+ textContents = ""
+ startOffset = 0
+ endOffset = 0
+ if obj.queryText().getNSelections() > 0:
+ [textContents, startOffset, endOffset] = self.selectedText(obj)
+
+ # Unfortunately, Evolution doesn't use the FLOWS_FROM and
+ # FLOWS_TO relationships to easily allow us to get to previous
+ # and next text objects. Instead we have to move up the
+ # component hierarchy until we get to the object containing all
+ # the panels (with each line containing a single text item).
+ # We can then check in both directions to see if there is other
+ # contiguous text that is selected. We also have to jump over
+ # zero length (empty) text lines and continue checking on the
+ # other side.
+ #
+ container = obj.parent.parent
+ current = obj.parent.getIndexInParent()
+ morePossibleSelections = True
+ while morePossibleSelections:
+ morePossibleSelections = False
+ if (current-1) >= 0:
+ prevPanel = container[current-1]
+ try:
+ prevObj = prevPanel[0]
+ displayedText = self.substring(prevObj, 0, -1)
+ if len(displayedText) == 0:
+ current -= 1
+ morePossibleSelections = True
+ elif prevObj.queryText().getNSelections() > 0:
+ [newTextContents, start, end] = \
+ self.selectedText(prevObj)
+ textContents = newTextContents + " " + textContents
+ current -= 1
+ morePossibleSelections = True
+ except:
+ pass
+
+ current = obj.parent.getIndexInParent()
+ morePossibleSelections = True
+ while morePossibleSelections:
+ morePossibleSelections = False
+ if (current+1) < container.childCount:
+ nextPanel = container[current+1]
+ try:
+ nextObj = nextPanel[0]
+ displayedText = self.substring(nextObj, 0, -1)
+ if len(displayedText) == 0:
+ current += 1
+ morePossibleSelections = True
+ elif nextObj.queryText().getNSelections() > 0:
+ [newTextContents, start, end] = \
+ self.selectedText(nextObj)
+ textContents += " " + newTextContents
+ current += 1
+ morePossibleSelections = True
+ except:
+ pass
+
+ return [textContents, startOffset, endOffset]
+
+ def hasTextSelections(self, obj):
+ """Return an indication of whether this object has selected text.
+ Note that it's possible that this object has no text, but is part
+ of a selected text area. Because of this, we need to check the
+ objects on either side to see if they are none zero length and
+ have text selections.
+
+ Arguments:
+ - obj: the text object to start checking for selected text.
+
+ Returns: an indication of whether this object has selected text,
+ or adjacent text objects have selected text.
+ """
+
+ currentSelected = False
+ otherSelected = False
+ nSelections = obj.queryText().getNSelections()
+ if nSelections:
+ currentSelected = True
+ else:
+ otherSelected = False
+ displayedText = self.substring(obj, 0, -1)
+ if len(displayedText) == 0:
+ container = obj.parent.parent
+ current = obj.parent.getIndexInParent()
+ morePossibleSelections = True
+ while morePossibleSelections:
+ morePossibleSelections = False
+ if (current-1) >= 0:
+ prevPanel = container[current-1]
+ prevObj = prevPanel[0]
+ try:
+ prevObjText = prevObj.queryText()
+ except:
+ prevObjText = None
+
+ if prevObj and prevObjText:
+ if prevObjText.getNSelections() > 0:
+ otherSelected = True
+ else:
+ displayedText = prevObjText.getText(0, -1)
+ if len(displayedText) == 0:
+ current -= 1
+ morePossibleSelections = True
+
+ current = obj.parent.getIndexInParent()
+ morePossibleSelections = True
+ while morePossibleSelections:
+ morePossibleSelections = False
+ if (current+1) < container.childCount:
+ nextPanel = container[current+1]
+ nextObj = nextPanel[0]
+ try:
+ nextObjText = nextObj.queryText()
+ except:
+ nextObjText = None
+
+ if nextObj and nextObjText:
+ if nextObjText.getNSelections() > 0:
+ otherSelected = True
+ else:
+ displayedText = nextObjText.getText(0, -1)
+ if len(displayedText) == 0:
+ current += 1
+ morePossibleSelections = True
+
+ return [currentSelected, otherSelected]
+
+ #########################################################################
+ # #
+ # Miscellaneous Utilities #
+ # #
+ #########################################################################
+
+ def misspelledWordAndBody(self, suggestionsList, messagePanel):
+ """Gets the misspelled word from the spelling dialog and the
+ list of words from the message body.
+
+ Arguments:
+ - suggestionsList: the list of spelling suggestions from the
+ spellcheck dialog
+ - messagePanel: the panel containing the message being checked
+ for spelling
+
+ Returns [mispelledWord, messageBody]
+ """
+
+ misspelledWord, messageBody = "", []
+
+ # Look for the "Suggestions for "xxxxx" label in the spell
+ # checker dialog panel. Extract out the xxxxx. This will be
+ # the misspelled word.
+ #
+ text = self.displayedLabel(suggestionsList) or ""
+ words = text.split()
+ for word in words:
+ if word[0] in ["'", '"']:
+ misspelledWord = word[1:len(word) - 1]
+ break
+
+ if messagePanel != None:
+ allTextObjects = self.descendantsWithRole(
+ messagePanel, pyatspi.ROLE_TEXT)
+ for obj in allTextObjects:
+ for word in self.substring(obj, 0, -1).split():
+ messageBody.append(word)
+
+ return [misspelledWord, messageBody]
+
+ def speakBlankLine(self, obj):
+ """Returns True if a blank line should be spoken.
+ Otherwise, returns False.
+ """
+
+ # Get the the AccessibleText interrface.
+ try:
+ text = obj.queryText()
+ except NotImplementedError:
+ return False
+
+ # Get the line containing the caret
+ caretOffset = text.caretOffset
+ line = text.getTextAtOffset(caretOffset, \
+ pyatspi.TEXT_BOUNDARY_LINE_START)
+
+ debug.println(debug.LEVEL_FINEST,
+ "speakBlankLine: start=%d, end=%d, line=<%s>" % \
+ (line[1], line[2], line[0]))
+
+ # If this is a blank line, announce it if the user requested
+ # that blank lines be spoken.
+ if line[1] == 0 and line[2] == 0:
+ return settings.speakBlankLines
+ else:
+ return False
+
+ def timeForCalRow(self, row, noIncs):
+ """Return a string equivalent to the time of the given row in
+ the calendar day view. Each calendar row is equivalent to a
+ certain time interval (from 5 minutes upto 1 hour), with time
+ (row 0) starting at 12 am (midnight).
+
+ Arguments:
+ - row: the row number.
+ - noIncs: the number of equal increments that the 24 hour period
+ is divided into.
+
+ Returns the time as a string.
+ """
+
+ totalMins = timeIncrements[noIncs] * row
+
+ if totalMins < 720:
+ suffix = 'A.M.'
+ else:
+ totalMins -= 720
+ suffix = 'P.M.'
+
+ hrs = hours[totalMins / 60]
+ mins = minutes[totalMins % 60]
+
+ return hrs + ' ' + mins + ' ' + suffix
+
+# Values used to construct a time string for calendar appointments.
+#
+timeIncrements = {}
+timeIncrements[288] = 5
+timeIncrements[144] = 10
+timeIncrements[96] = 15
+timeIncrements[48] = 30
+timeIncrements[24] = 60
+
+minutes = {}
+minutes[0] = ''
+minutes[5] = '5'
+minutes[10] = '10'
+minutes[15] = '15'
+minutes[20] = '20'
+minutes[25] = '25'
+minutes[30] = '30'
+minutes[35] = '35'
+minutes[40] = '40'
+minutes[45] = '45'
+minutes[50] = '50'
+minutes[55] = '55'
+
+hours = ['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']
diff --git a/src/orca/scripts/apps/evolution/speech_generator.py b/src/orca/scripts/apps/evolution/speech_generator.py
index 1518f71..f41f0c3 100644
--- a/src/orca/scripts/apps/evolution/speech_generator.py
+++ b/src/orca/scripts/apps/evolution/speech_generator.py
@@ -50,7 +50,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
rolesList = [pyatspi.ROLE_TABLE_CELL, \
pyatspi.ROLE_TREE_TABLE, \
pyatspi.ROLE_UNKNOWN]
- if self._script.isDesiredFocusedItem(obj, rolesList):
+ if self._script.utilities.hasMatchingHierarchy(obj, rolesList):
state = obj.getState()
if state and state.contains(pyatspi.STATE_EXPANDABLE):
if state.contains(pyatspi.STATE_EXPANDED):
@@ -90,9 +90,9 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
except NotImplementedError:
parentTable = None
if parentTable and parentTable.nColumns > 1 \
- and not self._script.isLayoutOnly(obj.parent):
+ and not self._script.utilities.isLayoutOnly(obj.parent):
for i in range(0, parentTable.nColumns):
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
row = parentTable.getRowAtIndex(index)
cell = parentTable.getAccessibleAt(row, i)
if not cell:
@@ -119,8 +119,9 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
if notChecked:
continue
- descendant = self._script.getRealActiveDescendant(cell)
- text = self._script.getDisplayedText(descendant)
+ descendant = self._script.utilities.\
+ realActiveDescendant(cell)
+ text = self._script.utilities.displayedText(descendant)
if text == "Status":
# Translators: this in reference to an e-mail message
# status of having been read or unread.
@@ -137,12 +138,12 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
relation.
"""
- if not self._script.isWizard(obj):
+ if not self._script.utilities.isWizard(obj):
return speech_generator.SpeechGenerator.\
_generateUnrelatedLabels(self, obj, **args)
result = []
- labels = self._script.findUnrelatedLabels(obj)
+ labels = self._script.utilities.unrelatedLabels(obj)
for label in labels:
name = self._generateName(label, **args)
try:
@@ -153,11 +154,11 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
attr = text.getAttributes(0)
if attr[0]:
[charKeys, charDict] = \
- self._script.textAttrsToDictionary(attr[0])
+ self._script.utilities.stringToKeysAndDict(attr[0])
if charDict.get('weight', '400') == '800':
# It's a new "screen" in the Setup Assistant.
#
- name = self._script.getDisplayedText(label)
+ name = self._script.utilities.displayedText(label)
# Translators: this is the name of a setup
# assistant window/screen in Evolution.
#
diff --git a/src/orca/scripts/apps/gajim/script.py b/src/orca/scripts/apps/gajim/script.py
index 548819d..5c2e3fa 100644
--- a/src/orca/scripts/apps/gajim/script.py
+++ b/src/orca/scripts/apps/gajim/script.py
@@ -115,6 +115,7 @@ class Script(default.Script):
# events we need to present text added to the chatroom are
# missing.
#
- allPageTabs = self.findByRole(event.source, pyatspi.ROLE_PAGE_TAB)
+ allPageTabs = self.utilities.descendantsWithRole(
+ event.source, pyatspi.ROLE_PAGE_TAB)
default.Script.onWindowActivated(self, event)
diff --git a/src/orca/scripts/apps/gcalctool/script.py b/src/orca/scripts/apps/gcalctool/script.py
index a1f6f42..398500f 100644
--- a/src/orca/scripts/apps/gcalctool/script.py
+++ b/src/orca/scripts/apps/gcalctool/script.py
@@ -76,7 +76,8 @@ class Script(default.Script):
#
if not (self._resultsDisplay and self._statusLine) \
and event.source.getRole() == pyatspi.ROLE_FRAME:
- objs = self.findByRole(event.source, pyatspi.ROLE_EDITBAR)
+ objs = self.utilities.descendantsWithRole(
+ event.source, pyatspi.ROLE_EDITBAR)
if len(objs) == 0:
# Translators: this is an indication that Orca is unable to
# obtain the display of the gcalctool calculator, which is
@@ -87,13 +88,13 @@ class Script(default.Script):
self.displayBrailleMessage(contents)
else:
self._resultsDisplay = objs[0]
- contents = self.getText(self._resultsDisplay, 0, -1)
+ contents = self.utilities.substring(self._resultsDisplay, 0, -1)
self.displayBrailleMessage(contents)
# The status line in gcalctool 5.29 is a sibling of the
# edit bar.
#
- objs = self.findByRole(self._resultsDisplay.parent,
- pyatspi.ROLE_TEXT)
+ objs = self.utilities.descendantsWithRole(
+ self._resultsDisplay.parent, pyatspi.ROLE_TEXT)
for obj in objs:
if not obj.getState().contains(pyatspi.STATE_EDITABLE):
self._statusLine = obj
@@ -102,9 +103,10 @@ class Script(default.Script):
# The status line in gcalctool 5.28 is a label in the
# status bar in which text is inserted as need be.
#
- statusBar = self.findStatusBar(event.source)
+ statusBar = self.utilities.statusBar(event.source)
if statusBar:
- objs = self.findByRole(statusBar, pyatspi.ROLE_LABEL)
+ objs = self.utilities.descendantsWithRole(
+ statusBar, pyatspi.ROLE_LABEL)
if len(objs):
self._statusLine = objs[0]
@@ -120,8 +122,8 @@ class Script(default.Script):
# Always update the Braille display but only speak if the last
# key pressed was Return, space, or equals.
#
- if self.isSameObject(event.source, self._resultsDisplay):
- contents = self.getText(self._resultsDisplay, 0, -1)
+ if self.utilities.isSameObject(event.source, self._resultsDisplay):
+ contents = self.utilities.substring(self._resultsDisplay, 0, -1)
self.displayBrailleMessage(contents)
if (orca_state.lastInputEvent is None) \
@@ -185,7 +187,7 @@ class Script(default.Script):
self._lastProcessedKeyEvent = \
input_event.KeyboardEvent(orca_state.lastNonModifierKeyEvent)
- elif self.isSameObject(event.source, self._statusLine):
- contents = self.getText(self._statusLine, 0, -1)
+ elif self.utilities.isSameObject(event.source, self._statusLine):
+ contents = self.utilities.substring(self._statusLine, 0, -1)
speech.speak(contents)
return
diff --git a/src/orca/scripts/apps/gcalctool/speech_generator.py b/src/orca/scripts/apps/gcalctool/speech_generator.py
index 9c4acf9..6e9925d 100644
--- a/src/orca/scripts/apps/gcalctool/speech_generator.py
+++ b/src/orca/scripts/apps/gcalctool/speech_generator.py
@@ -49,7 +49,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
if obj.name:
name = obj.name
else:
- name = self._script.getDisplayedText(obj)
+ name = self._script.utilities.displayedText(obj)
if name:
return [name]
diff --git a/src/orca/scripts/apps/gedit/script.py b/src/orca/scripts/apps/gedit/script.py
index 1dca9bb..73977a5 100644
--- a/src/orca/scripts/apps/gedit/script.py
+++ b/src/orca/scripts/apps/gedit/script.py
@@ -148,7 +148,7 @@ class Script(default.Script):
if mode == pyatspi.TEXT_BOUNDARY_LINE_START or \
len(mystr) == 0 or mystr[len(mystr)-1] in '.?!':
- string = self.adjustForRepeats(string)
+ string = self.utilities.adjustForRepeats(string)
if string.decode("UTF-8").isupper():
voice = settings.voices[settings.UPPERCASE_VOICE]
else:
@@ -186,7 +186,7 @@ class Script(default.Script):
# If there is anything left unspoken, speak it now.
#
if len(string) != 0:
- string = self.adjustForRepeats(string)
+ string = self.utilities.adjustForRepeats(string)
if string.decode("UTF-8").isupper():
voice = settings.voices[settings.UPPERCASE_VOICE]
else:
@@ -219,7 +219,8 @@ class Script(default.Script):
# Spelling dialog. Look for the one that isn't a label to
# another component.
#
- allLabels = self.findByRole(panel, pyatspi.ROLE_LABEL)
+ allLabels = self.utilities.descendantsWithRole(
+ panel, pyatspi.ROLE_LABEL)
for label in allLabels:
# Translators: these are labels from the gedit spell checking
# dialog and must be the same strings gedit uses. We hate
@@ -240,7 +241,8 @@ class Script(default.Script):
# was called. If they are the same then we ignore it.
if self.textArea != None:
- allText = self.findByRole(self.textArea, pyatspi.ROLE_TEXT)
+ allText = self.utilities.descendantsWithRole(
+ self.textArea, pyatspi.ROLE_TEXT)
caretPosition = allText[0].queryText().caretOffset
debug.println(self.debugLevel, \
@@ -273,7 +275,7 @@ class Script(default.Script):
#
allTokens = []
for i in range(0, len(allText)):
- text = self.getText(allText[i], 0, -1)
+ text = self.utilities.substring(allText[i], 0, -1)
tokens = text.split()
allTokens += tokens
@@ -315,9 +317,9 @@ class Script(default.Script):
# we're forced to do so in this case.
#
tmp = obj.parent.parent
- if (self.isDesiredFocusedItem(obj, rolesList1) \
+ if (self.utilities.hasMatchingHierarchy(obj, rolesList1) \
and obj.name == _("Find")) \
- or (self.isDesiredFocusedItem(obj, rolesList2) \
+ or (self.utilities.hasMatchingHierarchy(obj, rolesList2) \
and tmp.parent.parent.parent.name == _("Find")):
return True
else:
@@ -358,7 +360,7 @@ class Script(default.Script):
pyatspi.ROLE_PAGE_TAB,
pyatspi.ROLE_PAGE_TAB_LIST,
pyatspi.ROLE_SPLIT_PANE]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
debug.println(self.debugLevel,
"gedit.locusOfFocusChanged - text area.")
@@ -382,7 +384,7 @@ class Script(default.Script):
pyatspi.ROLE_PANEL,
pyatspi.ROLE_FILLER,
pyatspi.ROLE_FRAME]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
tmp = event.source.parent.parent
frame = tmp.parent.parent
# Translators: this is the name of the "Check Spelling" window
@@ -415,12 +417,12 @@ class Script(default.Script):
pyatspi.ROLE_FILLER,
pyatspi.ROLE_PAGE_TAB,
pyatspi.ROLE_PAGE_TAB_LIST]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
debug.println(self.debugLevel,
"gedit.onStateChanged - print preview - page #.")
parent = event.source.parent
- label1 = self.getDisplayedText(parent[1])
- label2 = self.getDisplayedText(parent[2])
+ label1 = self.utilities.displayedText(parent[1])
+ label2 = self.utilities.displayedText(parent[2])
items = [label1, label2]
self.presentItemsInSpeech(items)
self.presentItemsInBraille(items)
@@ -461,7 +463,7 @@ class Script(default.Script):
pyatspi.ROLE_PANEL,
pyatspi.ROLE_FILLER,
pyatspi.ROLE_FRAME]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
frame = event.source.parent.parent.parent
# Translators: this is the name of the "Check Spelling" window
# in gedit and must be the same as what gedit uses. We hate
@@ -553,10 +555,10 @@ class Script(default.Script):
and orca_state.lastNonModifierKeyEvent \
and orca_state.lastNonModifierKeyEvent.event_string == "Return":
debug.println(self.debugLevel, "gedit.onCaretMoved - find dialog.")
- allComboBoxes = \
- self.findByRole(orca_state.locusOfFocus.getApplication(),
- pyatspi.ROLE_COMBO_BOX)
- phrase = self.getDisplayedText(allComboBoxes[0])
+ allComboBoxes = self.utilities.descendantsWithRole(
+ orca_state.locusOfFocus.getApplication(),
+ pyatspi.ROLE_COMBO_BOX)
+ phrase = self.utilities.displayedText(allComboBoxes[0])
[text, caretOffset, startOffset] = \
self.getTextLineAtCaret(event.source)
if text.lower().find(phrase) != -1:
diff --git a/src/orca/scripts/apps/gnome-mud.py b/src/orca/scripts/apps/gnome-mud.py
index 9fc30cf..c2c18b9 100644
--- a/src/orca/scripts/apps/gnome-mud.py
+++ b/src/orca/scripts/apps/gnome-mud.py
@@ -164,7 +164,7 @@ class Script(default.Script):
#locusOfFocus.
rolesList = [pyatspi.ROLE_TERMINAL,
pyatspi.ROLE_FILLER]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
if self.flatReviewContext:
self.toggleFlatReviewMode()
message = event.any_data
diff --git a/src/orca/scripts/apps/gnome-search-tool.py b/src/orca/scripts/apps/gnome-search-tool.py
index b90b85a..6d4af54 100644
--- a/src/orca/scripts/apps/gnome-search-tool.py
+++ b/src/orca/scripts/apps/gnome-search-tool.py
@@ -133,7 +133,7 @@ class Script(default.Script):
# is using. We hate keying off stuff like this, but we're forced
# to do so in this case.
#
- if self.isDesiredFocusedItem(event.source, rolesList) and \
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList) and \
event.source.name == _("Stop") and visible:
debug.println(self.debugLevel,
"gnome-search-tool.onNameChanged - " \
@@ -145,8 +145,9 @@ class Script(default.Script):
# list of files found, then get it now.
#
if not self.fileTable:
- frame = self.getTopLevel(event.source)
- allTables = self.findByRole(frame, pyatspi.ROLE_TABLE)
+ frame = self.utilities.topLevelObject(event.source)
+ allTables = self.utilities.descendantsWithRole(
+ frame, pyatspi.ROLE_TABLE)
self.fileTable = allTables[0]
gobject.idle_add(self._speakSearching)
@@ -161,8 +162,8 @@ class Script(default.Script):
# is using. We hate keying off stuff like this, but we're forced
# to do so in this case.
#
- if self.isDesiredFocusedItem(event.source, rolesList) and \
- event.source.name == _("Find") and visible and self.searching:
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList) \
+ and event.source.name == _("Find") and visible and self.searching:
debug.println(self.debugLevel,
"gnome-search-tool.onNameChanged - " \
+ "search completed.")
diff --git a/src/orca/scripts/apps/gnome-system-monitor.py b/src/orca/scripts/apps/gnome-system-monitor.py
index 5bc5fc0..1b254b7 100644
--- a/src/orca/scripts/apps/gnome-system-monitor.py
+++ b/src/orca/scripts/apps/gnome-system-monitor.py
@@ -77,15 +77,16 @@ class Script(default.Script):
pyatspi.ROLE_PAGE_TAB_LIST,
pyatspi.ROLE_FILLER,
pyatspi.ROLE_FRAME]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
debug.println(self.debugLevel,
"GNOME System Monitor.locusOfFocusChanged - page tab.")
context = []
- panels = self.findByRole(newLocusOfFocus, pyatspi.ROLE_PANEL)
+ panels = self.utilities.descendantsWithRole(
+ newLocusOfFocus, pyatspi.ROLE_PANEL)
for panel in panels:
if panel.name and len(panel.name) > 0:
context.append(panel.name)
- labels = self.findUnrelatedLabels(panel)
+ labels = self.utilities.unrelatedLabels(panel)
for label in labels:
context.append(label.name)
diff --git a/src/orca/scripts/apps/gnome-terminal.py b/src/orca/scripts/apps/gnome-terminal.py
index 4dd0d2f..057f054 100644
--- a/src/orca/scripts/apps/gnome-terminal.py
+++ b/src/orca/scripts/apps/gnome-terminal.py
@@ -127,9 +127,8 @@ class Script(default.Script):
- event: the Event
"""
- if orca_state.lastInputEvent \
- and isinstance(orca_state.lastInputEvent,
- input_event.KeyboardEvent):
+ if orca_state.lastInputEvent and orca_state.lastNonModifierKeyEvent \
+ and isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent):
event_string = orca_state.lastNonModifierKeyEvent.event_string
else:
event_string = None
@@ -279,7 +278,7 @@ class Script(default.Script):
speech.speak(text)
if settings.enableEchoByWord \
- and self.isWordDelimiter(text.decode("UTF-8")[-1:]):
+ and self.utilities.isWordDelimiter(text.decode("UTF-8")[-1:]):
if matchFound:
self.echoPreviousWord(event.source)
diff --git a/src/orca/scripts/apps/gtk-window-decorator.py b/src/orca/scripts/apps/gtk-window-decorator.py
index fe0fdde..e996b24 100644
--- a/src/orca/scripts/apps/gtk-window-decorator.py
+++ b/src/orca/scripts/apps/gtk-window-decorator.py
@@ -74,7 +74,7 @@ class Script(default.Script):
# not.
#
found = False
- for app in self.getKnownApplications():
+ for app in self.utilities.knownApplications():
for child in app:
if child.name.startswith(objName):
found = True
diff --git a/src/orca/scripts/apps/liferea.py b/src/orca/scripts/apps/liferea.py
index 12dcc00..2bf7f1e 100644
--- a/src/orca/scripts/apps/liferea.py
+++ b/src/orca/scripts/apps/liferea.py
@@ -68,7 +68,8 @@ class Script(default.Script):
# We only speak the statusbar's changes when the application is
# with the focus and is the "work online/offline button is focused.
#
- if self.isDesiredFocusedItem(orca_state.locusOfFocus, rolesList):
+ if self.utilities.hasMatchingHierarchy(
+ orca_state.locusOfFocus, rolesList):
speech.stop()
speech.speak(event.source.name)
self.displayBrailleMessage(event.source.name)
@@ -112,7 +113,7 @@ class Script(default.Script):
# hierarchically located in the main window of the application
# (frame), inside a filler and inside another filler.
#
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
# If we are focusing this button we construct a utterance and
# a braille region to speak/braille "online/offline button".
# Here we declare utterances and add the localized string
@@ -143,7 +144,8 @@ class Script(default.Script):
if orca_state.locusOfFocus.getRole() == \
pyatspi.ROLE_TABLE_COLUMN_HEADER:
table = event.source.parent
- cells = self.findByRole(table, pyatspi.ROLE_TABLE_CELL)
+ cells = self.utilities.descendantsWithRole(
+ table, pyatspi.ROLE_TABLE_CELL)
eventsynthesizer.clickObject(cells[1], 1)
default.Script.locusOfFocusChanged(self, event,
diff --git a/src/orca/scripts/apps/metacity.py b/src/orca/scripts/apps/metacity.py
index 3e2e0c6..58a9b57 100644
--- a/src/orca/scripts/apps/metacity.py
+++ b/src/orca/scripts/apps/metacity.py
@@ -86,7 +86,7 @@ class Script(default.Script):
# not.
#
found = False
- for app in self.getKnownApplications():
+ for app in self.utilities.knownApplications():
i = 0
while i < app.childCount:
win = app.getChildAtIndex(i)
diff --git a/src/orca/scripts/apps/nautilus.py b/src/orca/scripts/apps/nautilus.py
index bb39fe1..ccf263b 100644
--- a/src/orca/scripts/apps/nautilus.py
+++ b/src/orca/scripts/apps/nautilus.py
@@ -108,13 +108,14 @@ class Script(default.Script):
itemCount = -1
itemCountString = " "
- allScrollPanes = self.findByRole(frame, pyatspi.ROLE_SCROLL_PANE)
- rolesList = [pyatspi.ROLE_SCROLL_PANE, \
- pyatspi.ROLE_FILLER, \
- pyatspi.ROLE_FILLER, \
- pyatspi.ROLE_SPLIT_PANE, \
- pyatspi.ROLE_PANEL, \
- pyatspi.ROLE_FRAME, \
+ allScrollPanes = self.utilities.descendantsWithRole(
+ frame, pyatspi.ROLE_SCROLL_PANE)
+ rolesList = [pyatspi.ROLE_SCROLL_PANE,
+ pyatspi.ROLE_FILLER,
+ pyatspi.ROLE_FILLER,
+ pyatspi.ROLE_SPLIT_PANE,
+ pyatspi.ROLE_PANEL,
+ pyatspi.ROLE_FRAME,
pyatspi.ROLE_APPLICATION]
# Look for the scroll pane containing the folder items. If this
@@ -123,7 +124,7 @@ class Script(default.Script):
# Create a string of the number of items in the folder.
#
for pane in allScrollPanes:
- if self.isDesiredFocusedItem(pane, rolesList):
+ if self.utilities.hasMatchingHierarchy(pane, rolesList):
for i in range(0, pane.childCount):
child = pane.getChildAtIndex(i)
if child.getRole() == pyatspi.ROLE_LAYERED_PANE:
@@ -173,17 +174,18 @@ class Script(default.Script):
allTokens = event.source.name.split(" - ")
newFolderName = allTokens[0]
- allPanels = self.findByRole(event.source, pyatspi.ROLE_PANEL)
- rolesList = [pyatspi.ROLE_PANEL, \
- pyatspi.ROLE_FILLER, \
- pyatspi.ROLE_PANEL, \
- pyatspi.ROLE_TOOL_BAR, \
- pyatspi.ROLE_PANEL, \
- pyatspi.ROLE_FRAME, \
+ allPanels = self.utilities.descendantsWithRole(
+ event.source, pyatspi.ROLE_PANEL)
+ rolesList = [pyatspi.ROLE_PANEL,
+ pyatspi.ROLE_FILLER,
+ pyatspi.ROLE_PANEL,
+ pyatspi.ROLE_TOOL_BAR,
+ pyatspi.ROLE_PANEL,
+ pyatspi.ROLE_FRAME,
pyatspi.ROLE_APPLICATION]
locationBarFound = False
for panel in allPanels:
- if self.isDesiredFocusedItem(panel, rolesList):
+ if self.utilities.hasMatchingHierarchy(panel, rolesList):
locationBarFound = True
desiredPanel = panel
break
@@ -194,7 +196,8 @@ class Script(default.Script):
child = desiredPanel.getChildAtIndex(i)
if child.getRole() == pyatspi.ROLE_TOGGLE_BUTTON and \
child.getState().contains(pyatspi.STATE_CHECKED):
- if not self.isSameObject(child, self.pathChild):
+ if not self.utilities.isSameObject(
+ child, self.pathChild):
self.pathChild = child
shouldAnnounce = True
break
@@ -242,7 +245,7 @@ class Script(default.Script):
pyatspi.ROLE_PANEL, \
pyatspi.ROLE_FRAME, \
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
debug.println(self.debugLevel, "nautilus.onStateChanged - " \
+ "Location: label.")
return
diff --git a/src/orca/scripts/apps/notification-daemon.py b/src/orca/scripts/apps/notification-daemon.py
index 0abb8aa..5066587 100644
--- a/src/orca/scripts/apps/notification-daemon.py
+++ b/src/orca/scripts/apps/notification-daemon.py
@@ -56,9 +56,9 @@ class Script(default.Script):
Arguments:
- event: the Event.
"""
- a = self.findByRole(event.source, pyatspi.ROLE_LABEL)
+ a = self.utilities.descendantsWithRole(event.source, pyatspi.ROLE_LABEL)
print a
- texts = [self.getDisplayedText(acc) for acc in a]
+ texts = [self.utilities.displayedText(acc) for acc in a]
# Translators: This denotes a notification to the user of some sort.
#
text = _('Notification %s') % ' '.join(texts)
diff --git a/src/orca/scripts/apps/packagemanager/Makefile.am b/src/orca/scripts/apps/packagemanager/Makefile.am
index 3daf180..286a824 100644
--- a/src/orca/scripts/apps/packagemanager/Makefile.am
+++ b/src/orca/scripts/apps/packagemanager/Makefile.am
@@ -1,11 +1,12 @@
orca_pathdir=$(pyexecdir)
orca_python_PYTHON = \
- __init__.py \
- braille_generator.py \
- script.py \
- script_settings.py \
- speech_generator.py \
- tutorialgenerator.py
+ __init__.py \
+ braille_generator.py \
+ script.py \
+ script_settings.py \
+ script_utilities.py \
+ speech_generator.py \
+ tutorialgenerator.py
orca_pythondir=$(pyexecdir)/orca/scripts/apps/packagemanager
\ No newline at end of file
diff --git a/src/orca/scripts/apps/packagemanager/script.py b/src/orca/scripts/apps/packagemanager/script.py
index 99cad2f..c79e6fb 100644
--- a/src/orca/scripts/apps/packagemanager/script.py
+++ b/src/orca/scripts/apps/packagemanager/script.py
@@ -42,6 +42,8 @@ from orca.orca_i18n import _
from braille_generator import BrailleGenerator
from speech_generator import SpeechGenerator
from tutorialgenerator import TutorialGenerator
+from script_utilities import Utilities
+
import script_settings
########################################################################
@@ -91,6 +93,11 @@ class Script(default.Script):
return TutorialGenerator(self)
+ def getUtilities(self):
+ """Returns the utilites for this script."""
+
+ return Utilities(self)
+
def getAppPreferencesGUI(self):
"""Return a GtkVBox contain the application unique configuration
GUI items for the current application.
@@ -149,7 +156,8 @@ class Script(default.Script):
and orca_state.lastNonModifierKeyEvent.event_string in \
["Left", "Right", "Up", "Down"] \
and event and event.type.startswith("focus:") \
- and (self.isLink(oldLocusOfFocus) or self.isLink(newLocusOfFocus)):
+ and (self.utilities.isLink(oldLocusOfFocus) \
+ or self.utilities.isLink(newLocusOfFocus)):
orca.setLocusOfFocus(event, newLocusOfFocus, False)
return
@@ -175,9 +183,8 @@ class Script(default.Script):
# script doesn't ignore this event.
#
if event.source != orca_state.locusOfFocus \
- and self.getAncestor(event.source,
- [pyatspi.ROLE_HTML_CONTAINER],
- [pyatspi.ROLE_FRAME]):
+ and self.utilities.ancestorWithRole(
+ event.source, [pyatspi.ROLE_HTML_CONTAINER], [pyatspi.ROLE_FRAME]):
orca.setLocusOfFocus(event, event.source, False)
default.Script.onCaretMoved(self, event)
@@ -241,7 +248,7 @@ class Script(default.Script):
and event.type.startswith("object:state-changed:showing") \
and event.detail1:
obj = self.findStatusBarIcon()
- while obj and not self.isSameObject(obj, event.source):
+ while obj and not self.utilities.isSameObject(obj, event.source):
obj = obj.parent
if obj:
# Translators: The Package Manager application notifies the
@@ -270,41 +277,6 @@ class Script(default.Script):
default.Script.onWindowActivated(self, event)
- def findStatusBar(self, obj):
- """Returns the status bar in the window which contains obj.
- Overridden here because Packagemanager seems to have multiple
- status bars which claim to be SHOWING and VISIBLE. The one we
- want should be displaying text, whereas the others are not.
- """
-
- # There are some objects which are not worth descending.
- #
- skipRoles = [pyatspi.ROLE_TREE,
- pyatspi.ROLE_TREE_TABLE,
- pyatspi.ROLE_TABLE]
-
- if obj.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS) \
- or obj.getRole() in skipRoles:
- return
-
- statusBar = None
- for i in range(obj.childCount - 1, -1, -1):
- if obj[i].getRole() == pyatspi.ROLE_STATUS_BAR:
- statusBar = obj[i]
- elif not obj[i] in skipRoles:
- statusBar = self.findStatusBar(obj[i])
-
- if statusBar:
- try:
- text = statusBar.queryText()
- except:
- pass
- else:
- if text.characterCount:
- break
-
- return statusBar
-
def findStatusBarIcon(self, statusBar=None):
"""Locates the icon which is sometimes found to the left of the
packagemanager status bar.
@@ -320,12 +292,12 @@ class Script(default.Script):
if not statusBar and self.app.childCount:
# Be sure we're looking in PM's main window.
#
- statusBar = self.findStatusBar(self.app[0])
+ statusBar = self.utilities.statusBar(self.app[0])
if statusBar:
i = statusBar.getIndexInParent()
if i > 0:
- icons = self.findByRole(
+ icons = self.utilities.descendantsWithRole(
statusBar.parent[i - 1], pyatspi.ROLE_ICON)
if icons:
icon = icons[0]
@@ -398,8 +370,9 @@ class Script(default.Script):
default.Script._presentTextAtNewCaretPosition(self, event, otherObj)
- if not self.isSameObject(event.source, self._lastObjectPresented) \
- and self.getAncestor(
+ if not self.utilities.isSameObject(
+ event.source, self._lastObjectPresented) \
+ and self.utilities.ancestorWithRole(
event.source, [pyatspi.ROLE_HTML_CONTAINER], [pyatspi.ROLE_FRAME]):
self.updateBraille(event.source)
@@ -413,7 +386,7 @@ class Script(default.Script):
- extra: extra Region to add to the end
"""
- if not self.getAncestor(
+ if not self.utilities.ancestorWithRole(
obj, [pyatspi.ROLE_HTML_CONTAINER], [pyatspi.ROLE_FRAME]):
return default.Script.updateBraille(self, obj, extraRegion)
@@ -427,7 +400,7 @@ class Script(default.Script):
contents = self.getLineContentsAtOffset(obj, text.caretOffset)
for i, content in enumerate(contents):
child, startOffset, endOffset, string = content
- isFocusedObj = self.isSameObject(child, obj)
+ isFocusedObj = self.utilities.isSameObject(child, obj)
regions = [self.getNewBrailleText(child,
startOffset=startOffset,
endOffset=endOffset)]
@@ -452,7 +425,7 @@ class Script(default.Script):
interface
"""
- if not self.getAncestor(
+ if not self.utilities.ancestorWithRole(
obj, [pyatspi.ROLE_HTML_CONTAINER], [pyatspi.ROLE_FRAME]):
return default.Script.sayCharacter(self, obj)
@@ -474,7 +447,7 @@ class Script(default.Script):
def sayLine(self, obj):
"""Speaks the line at the current caret position."""
- if not self.getAncestor(
+ if not self.utilities.ancestorWithRole(
obj, [pyatspi.ROLE_HTML_CONTAINER], [pyatspi.ROLE_FRAME]):
return default.Script.sayLine(self, obj)
@@ -489,7 +462,7 @@ class Script(default.Script):
if len(contents) > 1 and not line.strip():
continue
- isLink = self.isLink(child)
+ isLink = self.utilities.isLink(child)
if len(line) and line != "\n":
if line.decode("UTF-8").isupper():
@@ -499,7 +472,7 @@ class Script(default.Script):
else:
voice = self.voices[settings.DEFAULT_VOICE]
- line = self.adjustForRepeats(line)
+ line = self.utilities.adjustForRepeats(line)
speech.speak(line, voice)
if isLink:
speech.speak(self.speechGenerator.getRoleName(
@@ -632,45 +605,6 @@ class Script(default.Script):
verticalCenter2 = extents2[1] + extents2[3] / 2
return abs(verticalCenter1 - verticalCenter2) <= 5
- def isLink(self, obj):
- """Returns True if this is a text object serving as a link.
-
- Arguments:
- - obj: an accessible
- """
-
- if not obj:
- return False
-
- # Images seem to be exposed as ROLE_PANEL and implement very few of
- # the accessibility interfaces.
- #
- if obj.getRole() == pyatspi.ROLE_PANEL and not obj.childCount \
- and obj.getState().contains(pyatspi.STATE_FOCUSABLE) \
- and self.getAncestor(obj,
- [pyatspi.ROLE_HTML_CONTAINER],
- [pyatspi.ROLE_FRAME]):
- return True
-
- try:
- text = obj.queryText()
- except:
- return False
- else:
- return self.getLinkIndex(obj, text.caretOffset) >= 0
-
- def isTextArea(self, obj):
- """Returns True if obj is a GUI component that is for entering text.
-
- Arguments:
- - obj: an accessible
- """
-
- if self.isLink(obj):
- return False
-
- return default.Script.isTextArea(self, obj)
-
def isSearchEntry(self, obj):
"""Attempts to distinguish the Search entry from other accessibles.
@@ -684,7 +618,7 @@ class Script(default.Script):
# should change, we'll need to make our criteria more specific.
#
if obj and obj.getRole() == pyatspi.ROLE_TEXT \
- and self.getAncestor( \
+ and self.utilities.ancestorWithRole(
obj, [pyatspi.ROLE_TOOL_BAR], [pyatspi.ROLE_FRAME]):
return True
@@ -717,10 +651,10 @@ class Script(default.Script):
except:
col = -1
else:
- index = self.getCellIndex(obj)
+ index = self.utilities.cellIndex(obj)
col = table.getColumnAtIndex(index)
if col == 0:
- top = self.getTopLevel(obj)
+ top = self.utilities.topLevelObject(obj)
if top and top.getRole() == pyatspi.ROLE_FRAME:
return True
return False
diff --git a/src/orca/scripts/apps/packagemanager/script_utilities.py b/src/orca/scripts/apps/packagemanager/script_utilities.py
new file mode 100644
index 0000000..8445236
--- /dev/null
+++ b/src/orca/scripts/apps/packagemanager/script_utilities.py
@@ -0,0 +1,135 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Commonly-required utility methods needed by -- and potentially
+ customized by -- application and toolkit scripts. They have
+ been pulled out from the scripts because certain scripts had
+ gotten way too large as a result of including these methods."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__ = "LGPL"
+
+import pyatspi
+
+import orca.script_utilities as script_utilities
+
+#############################################################################
+# #
+# Utilities #
+# #
+#############################################################################
+
+class Utilities(script_utilities.Utilities):
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ script_utilities.Utilities.__init__(self, script)
+
+ #########################################################################
+ # #
+ # Utilities for finding, identifying, and comparing accessibles #
+ # #
+ #########################################################################
+
+ def isLink(self, obj):
+ """Returns True if this is a text object serving as a link.
+
+ Arguments:
+ - obj: an accessible
+ """
+
+ if not obj:
+ return False
+
+ # Images seem to be exposed as ROLE_PANEL and implement very few of
+ # the accessibility interfaces.
+ #
+ if obj.getRole() == pyatspi.ROLE_PANEL and not obj.childCount \
+ and obj.getState().contains(pyatspi.STATE_FOCUSABLE) \
+ and self.ancestorWithRole(
+ obj, [pyatspi.ROLE_HTML_CONTAINER], [pyatspi.ROLE_FRAME]):
+ return True
+
+ try:
+ text = obj.queryText()
+ except:
+ return False
+ else:
+ return self.linkIndex(obj, text.caretOffset) >= 0
+
+ def statusBar(self, obj):
+ """Returns the status bar in the window which contains obj.
+ Overridden here because Packagemanager seems to have multiple
+ status bars which claim to be SHOWING and VISIBLE. The one we
+ want should be displaying text, whereas the others are not.
+
+ Arguments:
+ - obj: the top-level object (e.g. window, frame, dialog) for
+ which the status bar is sought.
+ """
+
+ # There are some objects which are not worth descending.
+ #
+ skipRoles = [pyatspi.ROLE_TREE,
+ pyatspi.ROLE_TREE_TABLE,
+ pyatspi.ROLE_TABLE]
+
+ if obj.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS) \
+ or obj.getRole() in skipRoles:
+ return
+
+ statusBar = None
+ for i in range(obj.childCount - 1, -1, -1):
+ if obj[i].getRole() == pyatspi.ROLE_STATUS_BAR:
+ statusBar = obj[i]
+ elif not obj[i] in skipRoles:
+ statusBar = self.statusBar(obj[i])
+
+ if statusBar:
+ try:
+ text = statusBar.queryText()
+ except:
+ pass
+ else:
+ if text.characterCount:
+ break
+
+ return statusBar
+
+ #########################################################################
+ # #
+ # Utilities for working with the accessible text interface #
+ # #
+ #########################################################################
+
+
+
+ #########################################################################
+ # #
+ # Miscellaneous Utilities #
+ # #
+ #########################################################################
diff --git a/src/orca/scripts/apps/packagemanager/speech_generator.py b/src/orca/scripts/apps/packagemanager/speech_generator.py
index d0793f3..8d4dad4 100644
--- a/src/orca/scripts/apps/packagemanager/speech_generator.py
+++ b/src/orca/scripts/apps/packagemanager/speech_generator.py
@@ -40,7 +40,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
def generateSpeech(self, obj, **args):
results = []
oldRole = None
- if self._script.isLink(obj):
+ if self._script.utilities.isLink(obj):
oldRole = self._overrideRole(pyatspi.ROLE_LINK, args)
results.extend(
speech_generator.SpeechGenerator.generateSpeech(self, obj, **args))
@@ -82,7 +82,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
inactive). Otherwise, and empty array will be returned.
"""
result = []
- if not self._script.isLink(obj):
+ if not self._script.utilities.isLink(obj):
result.extend(speech_generator.SpeechGenerator.\
_generateAvailability(self, obj, **args))
return result
@@ -93,7 +93,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
This method should initially be called with a top-level window.
"""
result = []
- statusBar = self._script.findStatusBar(obj)
+ statusBar = self._script.utilities.statusBar(obj)
if statusBar:
name = self._generateName(statusBar)
if name:
diff --git a/src/orca/scripts/apps/pidgin/Makefile.am b/src/orca/scripts/apps/pidgin/Makefile.am
index b18096d..10ea6d4 100644
--- a/src/orca/scripts/apps/pidgin/Makefile.am
+++ b/src/orca/scripts/apps/pidgin/Makefile.am
@@ -4,6 +4,7 @@ orca_python_PYTHON = \
__init__.py \
script.py \
script_settings.py \
+ script_utilities.py \
speech_generator.py
orca_pythondir=$(pyexecdir)/orca/scripts/apps/pidgin
diff --git a/src/orca/scripts/apps/pidgin/script.py b/src/orca/scripts/apps/pidgin/script.py
index 0fc8608..340add1 100644
--- a/src/orca/scripts/apps/pidgin/script.py
+++ b/src/orca/scripts/apps/pidgin/script.py
@@ -46,6 +46,7 @@ import orca.speech as speech
from orca.orca_i18n import _
+from script_utilities import Utilities
from speech_generator import SpeechGenerator
import script_settings
@@ -259,6 +260,11 @@ class Script(default.Script):
return SpeechGenerator(self)
+ def getUtilities(self):
+ """Returns the utilites for this script."""
+
+ return Utilities(self)
+
def getAppPreferencesGUI(self):
"""Return a GtkVBox contain the application unique configuration
GUI items for the current application.
@@ -537,7 +543,7 @@ class Script(default.Script):
if script_settings.chatRoomHistories:
chatRoomTab = self.getChatRoomTab(orca_state.locusOfFocus)
- chatRoomName = self.getDisplayedText(chatRoomTab)
+ chatRoomName = self.utilities.displayedText(chatRoomTab)
if not chatRoomName in self.chatRoomMessages:
return
messages = self.chatRoomMessages[chatRoomName].get()
@@ -587,7 +593,7 @@ class Script(default.Script):
rolesList = [pyatspi.ROLE_PAGE_TAB_LIST, \
pyatspi.ROLE_FILLER, \
pyatspi.ROLE_FRAME]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
# As it's possible to get this component hierarchy in other
# places than the chat room (i.e. the Preferences dialog),
# we check to see if the name of the frame is the same as one
@@ -624,7 +630,7 @@ class Script(default.Script):
pyatspi.ROLE_FILLER, \
pyatspi.ROLE_PAGE_TAB, \
pyatspi.ROLE_PAGE_TAB_LIST]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
isBuddyListEvent = True
return isBuddyListEvent
@@ -716,9 +722,8 @@ class Script(default.Script):
# text field. Hopefully this is true for other types of chat
# as well, but is currently untested.
#
- allTextFields = self.findByRole(chatRoomTab,
- pyatspi.ROLE_TEXT,
- False)
+ allTextFields = self.utilities.descendantsWithRole(
+ chatRoomTab, pyatspi.ROLE_TEXT, False)
index = len(allTextFields) - 2
if index >= 0:
self.chatAreas[chatRoomTab] = allTextFields[index]
@@ -730,7 +735,7 @@ class Script(default.Script):
# (if one doesn't already exist) and populate it with empty
# strings.
#
- chatRoomName = self.getDisplayedText(chatRoomTab)
+ chatRoomName = self.utilities.displayedText(chatRoomTab)
if not chatRoomName in self.chatRoomMessages:
self.chatRoomMessages[chatRoomName] = \
RingList(Script.MESSAGE_LIST_LENGTH)
@@ -746,13 +751,13 @@ class Script(default.Script):
if self.flatReviewContext:
self.toggleFlatReviewMode()
- message = self.getText(event.source,
- event.detail1,
- event.detail1 + event.detail2)
+ message = self.utilities.substring(event.source,
+ event.detail1,
+ event.detail1 + event.detail2)
if message and message[0] == "\n":
message = message[1:]
- chatRoomName = self.getDisplayedText(chatRoomTab)
+ chatRoomName = self.utilities.displayedText(chatRoomTab)
# If the user doesn't want announcements for when their buddies
# are typing (or have stopped typing), and this is such a message,
@@ -763,7 +768,7 @@ class Script(default.Script):
# stream of "is typing" updates.
#
attr, start, end = \
- self.getTextAttributes(event.source, event.detail1)
+ self.utilities.textAttributes(event.source, event.detail1)
if float(attr.get('scale', '1')) < 1 \
or int(attr.get('weight', '400')) < 400:
if not script_settings.announceBuddyTyping or \
@@ -788,7 +793,7 @@ class Script(default.Script):
# We don't want to do this for the status messages however.
#
if not self.lastStatus:
- chatRoomName = self.getDisplayedText(chatRoomTab)
+ chatRoomName = self.utilities.displayedText(chatRoomTab)
self.allPreviousMessages.append(message)
self.previousChatRoomNames.append(chatRoomName)
@@ -828,118 +833,8 @@ class Script(default.Script):
pyatspi.ROLE_FILLER,
pyatspi.ROLE_PAGE_TAB]
- return self.isDesiredFocusedItem(obj, rolesList)
+ return self.utilities.hasMatchingHierarchy(obj, rolesList)
- def getNodeLevel(self, obj):
- """Determines the node level of this object if it is in a tree
- relation, with 0 being the top level node. If this object is
- not in a tree relation, then -1 will be returned. Overridden
- here because the accessible we need is in a hidden column.
-
- Arguments:
- -obj: the Accessible object
- """
-
- if not obj:
- return -1
-
- if not self.isInBuddyList(obj):
- return default.Script.getNodeLevel(self, obj)
-
- try:
- obj = obj.parent[obj.getIndexInParent() - 1]
- except:
- return -1
-
- try:
- table = obj.parent.queryTable()
- except:
- return -1
-
- nodes = []
- node = obj
- done = False
- while not done:
- relations = node.getRelationSet()
- node = None
- for relation in relations:
- if relation.getRelationType() \
- == pyatspi.RELATION_NODE_CHILD_OF:
- node = relation.getTarget(0)
- break
-
- # We want to avoid situations where something gives us an
- # infinite cycle of nodes. Bon Echo has been seen to do
- # this (see bug 351847).
- #
- if (len(nodes) > 100) or nodes.count(node):
- debug.println(debug.LEVEL_WARNING,
- "pidgin.getNodeLevel detected a cycle!!!")
- done = True
- elif node:
- nodes.append(node)
- debug.println(debug.LEVEL_FINEST,
- "pidgin.getNodeLevel %d" % len(nodes))
- else:
- done = True
-
- return len(nodes) - 1
-
- def getChildNodes(self, obj):
- """Gets all of the children that have RELATION_NODE_CHILD_OF pointing
- to this expanded table cell. Overridden here because the object
- which contains the relation is in a hidden column and thus doesn't
- have a column number (necessary for using getAccessibleAt()).
-
- Arguments:
- -obj: the Accessible Object
-
- Returns: a list of all the child nodes
- """
-
- if not self.isInBuddyList(obj):
- return default.Script.getChildNodes(self, obj)
-
- try:
- table = obj.parent.queryTable()
- except:
- return []
- else:
- if not obj.getState().contains(pyatspi.STATE_EXPANDED):
- return []
-
- nodes = []
- index = self.getCellIndex(obj)
- row = table.getRowAtIndex(index)
- col = table.getColumnAtIndex(index + 1)
- nodeLevel = self.getNodeLevel(obj)
- done = False
-
- # Candidates will be in the rows beneath the current row.
- # Only check in the current column and stop checking as
- # soon as the node level of a candidate is equal or less
- # than our current level.
- #
- for i in range(row+1, table.nRows):
- cell = table.getAccessibleAt(i, col)
- nodeCell = cell.parent[cell.getIndexInParent() - 1]
- relations = nodeCell.getRelationSet()
- for relation in relations:
- if relation.getRelationType() \
- == pyatspi.RELATION_NODE_CHILD_OF:
- nodeOf = relation.getTarget(0)
- if self.isSameObject(obj, nodeOf):
- nodes.append(cell)
- else:
- currentLevel = self.getNodeLevel(nodeOf)
- if currentLevel <= nodeLevel:
- done = True
- break
- if done:
- break
-
- return nodes
-
def visualAppearanceChanged(self, event, obj):
"""Called when the visual appearance of an object changes.
Overridden here because we get object:state-changed:expanded
diff --git a/src/orca/scripts/apps/pidgin/script_utilities.py b/src/orca/scripts/apps/pidgin/script_utilities.py
new file mode 100644
index 0000000..044d094
--- /dev/null
+++ b/src/orca/scripts/apps/pidgin/script_utilities.py
@@ -0,0 +1,181 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Commonly-required utility methods needed by -- and potentially
+ customized by -- application and toolkit scripts. They have
+ been pulled out from the scripts because certain scripts had
+ gotten way too large as a result of including these methods."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__ = "LGPL"
+
+import pyatspi
+
+import orca.debug as debug
+import orca.script_utilities as script_utilities
+
+#############################################################################
+# #
+# Utilities #
+# #
+#############################################################################
+
+class Utilities(script_utilities.Utilities):
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ script_utilities.Utilities.__init__(self, script)
+
+ #########################################################################
+ # #
+ # Utilities for finding, identifying, and comparing accessibles #
+ # #
+ #########################################################################
+
+ def childNodes(self, obj):
+ """Gets all of the children that have RELATION_NODE_CHILD_OF pointing
+ to this expanded table cell. Overridden here because the object
+ which contains the relation is in a hidden column and thus doesn't
+ have a column number (necessary for using getAccessibleAt()).
+
+ Arguments:
+ -obj: the Accessible Object
+
+ Returns: a list of all the child nodes
+ """
+
+ if not self._script.isInBuddyList(obj):
+ return script_utilities.Utilities.childNodes(self, obj)
+
+ try:
+ table = obj.parent.queryTable()
+ except:
+ return []
+ else:
+ if not obj.getState().contains(pyatspi.STATE_EXPANDED):
+ return []
+
+ nodes = []
+ index = self.cellIndex(obj)
+ row = table.getRowAtIndex(index)
+ col = table.getColumnAtIndex(index + 1)
+ nodeLevel = self.nodeLevel(obj)
+ done = False
+
+ # Candidates will be in the rows beneath the current row.
+ # Only check in the current column and stop checking as
+ # soon as the node level of a candidate is equal or less
+ # than our current level.
+ #
+ for i in range(row+1, table.nRows):
+ cell = table.getAccessibleAt(i, col)
+ nodeCell = cell.parent[cell.getIndexInParent() - 1]
+ relations = nodeCell.getRelationSet()
+ for relation in relations:
+ if relation.getRelationType() \
+ == pyatspi.RELATION_NODE_CHILD_OF:
+ nodeOf = relation.getTarget(0)
+ if self.isSameObject(obj, nodeOf):
+ nodes.append(cell)
+ else:
+ currentLevel = self.nodeLevel(nodeOf)
+ if currentLevel <= nodeLevel:
+ done = True
+ break
+ if done:
+ break
+
+ return nodes
+
+ def nodeLevel(self, obj):
+ """Determines the node level of this object if it is in a tree
+ relation, with 0 being the top level node. If this object is
+ not in a tree relation, then -1 will be returned. Overridden
+ here because the accessible we need is in a hidden column.
+
+ Arguments:
+ -obj: the Accessible object
+ """
+
+ if not obj:
+ return -1
+
+ if not self._script.isInBuddyList(obj):
+ return script_utilities.Utilities.nodeLevel(self, obj)
+
+ try:
+ obj = obj.parent[obj.getIndexInParent() - 1]
+ except:
+ return -1
+
+ try:
+ table = obj.parent.queryTable()
+ except:
+ return -1
+
+ nodes = []
+ node = obj
+ done = False
+ while not done:
+ relations = node.getRelationSet()
+ node = None
+ for relation in relations:
+ if relation.getRelationType() \
+ == pyatspi.RELATION_NODE_CHILD_OF:
+ node = relation.getTarget(0)
+ break
+
+ # We want to avoid situations where something gives us an
+ # infinite cycle of nodes. Bon Echo has been seen to do
+ # this (see bug 351847).
+ #
+ if (len(nodes) > 100) or nodes.count(node):
+ debug.println(debug.LEVEL_WARNING,
+ "pidgin.nodeLevel detected a cycle!!!")
+ done = True
+ elif node:
+ nodes.append(node)
+ debug.println(debug.LEVEL_FINEST,
+ "pidgin.nodeLevel %d" % len(nodes))
+ else:
+ done = True
+
+ return len(nodes) - 1
+
+ #########################################################################
+ # #
+ # Utilities for working with the accessible text interface #
+ # #
+ #########################################################################
+
+
+
+ #########################################################################
+ # #
+ # Miscellaneous Utilities #
+ # #
+ #########################################################################
diff --git a/src/orca/scripts/apps/planner/braille_generator.py b/src/orca/scripts/apps/planner/braille_generator.py
index b4b643b..405caf2 100644
--- a/src/orca/scripts/apps/planner/braille_generator.py
+++ b/src/orca/scripts/apps/planner/braille_generator.py
@@ -66,7 +66,7 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
if handleRibbonButton:
result.append(_("Display more options"))
elif handleTabButton:
- result.append(self._script.getDisplayedText(obj.parent[1]))
+ result.append(self._script.utilities.displayedText(obj.parent[1]))
else:
result.extend(
braille_generator.BrailleGenerator._generateDisplayedText(
diff --git a/src/orca/scripts/apps/planner/speech_generator.py b/src/orca/scripts/apps/planner/speech_generator.py
index c405202..b263c23 100644
--- a/src/orca/scripts/apps/planner/speech_generator.py
+++ b/src/orca/scripts/apps/planner/speech_generator.py
@@ -62,7 +62,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
if handleRibbonButton:
result.append(_("Display more options"))
elif handleTabButton:
- result.append(self._script.getDisplayedText(obj.parent[1]))
+ result.append(self._script.utilities.displayedText(obj.parent[1]))
else:
result.extend(
speech_generator.SpeechGenerator._generateLabelAndName(
diff --git a/src/orca/scripts/apps/soffice/Makefile.am b/src/orca/scripts/apps/soffice/Makefile.am
index d17a93d..cb4c40a 100644
--- a/src/orca/scripts/apps/soffice/Makefile.am
+++ b/src/orca/scripts/apps/soffice/Makefile.am
@@ -6,6 +6,7 @@ orca_python_PYTHON = \
formatting.py \
script.py \
script_settings.py \
+ script_utilities.py \
speech_generator.py \
structural_navigation.py
diff --git a/src/orca/scripts/apps/soffice/braille_generator.py b/src/orca/scripts/apps/soffice/braille_generator.py
index 0d3775d..1c36815 100644
--- a/src/orca/scripts/apps/soffice/braille_generator.py
+++ b/src/orca/scripts/apps/soffice/braille_generator.py
@@ -58,7 +58,7 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
except:
pass
else:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
rowIndex = table.getRowAtIndex(index)
if rowIndex >= 0 \
and table in self._script.dynamicRowHeaders:
@@ -70,11 +70,11 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
headerText = None
if header.childCount > 0:
for child in header:
- text = self._script.getText(child, 0, -1)
+ text = self._script.utilities.substring(child, 0, -1)
if text:
result.append(text)
elif headerText:
- text = self._script.getText(header, 0, -1)
+ text = self._script.utilities.substring(header, 0, -1)
if text:
result.append(text)
return result
@@ -91,7 +91,7 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
except:
pass
else:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
columnIndex = table.getColumnAtIndex(index)
if columnIndex >= 0 \
and table in self._script.dynamicColumnHeaders:
@@ -103,11 +103,11 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
headerText = None
if header.childCount > 0:
for child in header:
- text = self._script.getText(child, 0, -1)
+ text = self._script.utilities.substring(child, 0, -1)
if text:
result.append(text)
elif headerText:
- text = self._script.getText(header, 0, -1)
+ text = self._script.utilities.substring(header, 0, -1)
if text:
result.append(text)
return result
@@ -117,15 +117,14 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
if self._script.inputLineForCell == None:
self._script.inputLineForCell = \
self._script.locateInputLine(obj)
- # If the spread sheet table cell has something in it, then we
- # want to append the name of the cell (which will be its
- # location). Note that if the cell was empty, then
- # self._script.getDisplayedText will have already done this
- # for us.
+ # If the spread sheet table cell has something in it, then we want
+ # to append the name of the cell (which will be its location). Note
+ # that if the cell was empty, self._script.utilities.displayedText
+ # will have already done this for us.
#
try:
if obj.queryText():
- objectText = self._script.getText(obj, 0, -1)
+ objectText = self._script.utilities.substring(obj, 0, -1)
if objectText and len(objectText) != 0:
result.append(braille.Component(
obj, objectText + " " + obj.name))
@@ -188,7 +187,7 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
parent = obj.parent
parentTable = parent.queryTable()
if settings.readTableCellRow and parentTable:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
row = parentTable.getRowAtIndex(index)
column = parentTable.getColumnAtIndex(index)
# This is an indication of whether we should present all the
@@ -241,7 +240,7 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
pyatspi.ROLE_ROOT_PANE, \
pyatspi.ROLE_FRAME, \
pyatspi.ROLE_APPLICATION]
- if self._script.isDesiredFocusedItem(obj, rolesList):
+ if self._script.utilities.hasMatchingHierarchy(obj, rolesList):
for child in obj.parent:
if child.getRole() == pyatspi.ROLE_PAGE_TAB_LIST:
for tab in child:
diff --git a/src/orca/scripts/apps/soffice/script.py b/src/orca/scripts/apps/soffice/script.py
index 9de9e14..7c872b4 100644
--- a/src/orca/scripts/apps/soffice/script.py
+++ b/src/orca/scripts/apps/soffice/script.py
@@ -55,6 +55,7 @@ from speech_generator import SpeechGenerator
from braille_generator import BrailleGenerator
from formatting import Formatting
from structural_navigation import StructuralNavigation
+from script_utilities import Utilities
import script_settings
class Script(default.Script):
@@ -199,6 +200,11 @@ class Script(default.Script):
"""Returns the formatting strings for this script."""
return Formatting(self)
+ def getUtilities(self):
+ """Returns the utilites for this script."""
+
+ return Utilities(self)
+
def getStructuralNavigation(self):
"""Returns the 'structural navigation' class for this script.
"""
@@ -492,17 +498,6 @@ class Script(default.Script):
return False
- def isReadOnlyTextArea(self, obj):
- """Returns True if obj is a text entry area that is read only."""
- state = obj.getState()
- readOnly = obj.getRole() == pyatspi.ROLE_TEXT \
- and state.contains(pyatspi.STATE_FOCUSABLE) \
- and not state.contains(pyatspi.STATE_EDITABLE)
- debug.println(debug.LEVEL_ALL,
- "soffice.script.py:isReadOnlyTextArea=%s for %s" \
- % (readOnly, debug.getAccessibleDetails(obj)))
- return readOnly
-
def adjustForWriterTable(self, obj):
"""Check to see if we are in Writer, where the object with focus
is a paragraph, and the parent is the table cell. If it is, then,
@@ -562,7 +557,7 @@ class Script(default.Script):
parentTable = None
if parent and parentTable:
- index = self.getCellIndex(obj)
+ index = self.utilities.cellIndex(obj)
row = parentTable.getRowAtIndex(index)
accCell = parentTable.getAccessibleAt(row, column)
@@ -589,7 +584,7 @@ class Script(default.Script):
parentTable = None
if parent and parentTable:
- index = self.getCellIndex(obj)
+ index = self.utilities.cellIndex(obj)
column = parentTable.getColumnAtIndex(index)
accCell = parentTable.getAccessibleAt(row, column)
@@ -612,7 +607,8 @@ class Script(default.Script):
inputLine = None
panel = obj.parent.parent.parent.parent
if panel and panel.getRole() == pyatspi.ROLE_PANEL:
- allParagraphs = self.findByRole(panel, pyatspi.ROLE_PARAGRAPH)
+ allParagraphs = self.utilities.descendantsWithRole(
+ panel, pyatspi.ROLE_PARAGRAPH)
if len(allParagraphs) == 1:
inputLine = allParagraphs[0]
else:
@@ -652,7 +648,7 @@ class Script(default.Script):
parent.queryComponent().getAccessibleAtPoint(leftX, y, 0)
if leftCell:
table = leftCell.parent.queryTable()
- index = self.getCellIndex(leftCell)
+ index = self.utilities.cellIndex(leftCell)
startIndex = table.getColumnAtIndex(index)
rightX = extents.x + extents.width - 1
@@ -660,7 +656,7 @@ class Script(default.Script):
parent.queryComponent().getAccessibleAtPoint(rightX, y, 0)
if rightCell:
table = rightCell.parent.queryTable()
- index = self.getCellIndex(rightCell)
+ index = self.utilities.cellIndex(rightCell)
endIndex = table.getColumnAtIndex(index)
return [startIndex, endIndex]
@@ -690,139 +686,13 @@ class Script(default.Script):
# a carry-over from the whereAmI code.
#
if cell.getRole() == pyatspi.ROLE_PARAGRAPH:
- top = self.getTopLevel(cell)
+ top = self.utilities.topLevelObject(cell)
return (top and top.name.endswith(" Calc"))
else:
return False
else:
return table.nRows in [65536, 1048576]
- def isDesiredFocusedItem(self, obj, rolesList):
- """Called to determine if the given object and it's hierarchy of
- parent objects, each have the desired roles.
-
- This is an override because of bugs in OOo's child/parent symmetry.
- See Script._getParent().
-
- Arguments:
- - obj: the accessible object to check.
- - rolesList: the list of desired roles for the components and the
- hierarchy of its parents.
-
- Returns True if all roles match.
- """
- current = obj
- for role in rolesList:
- if current is None:
- return False
-
- if not isinstance(role, list):
- role = [role]
-
- if isinstance(role[0], str):
- current_role = current.getRoleName()
- else:
- current_role = current.getRole()
-
- if not current_role in role:
- return False
-
- current = self._getParent(current)
-
- return True
-
- def findFrameAndDialog(self, obj):
- """Returns the frame and (possibly) the dialog containing
- the object. Overridden here for presentation of the title
- bar information: If the locusOfFocus is a spreadsheet cell,
- 1) we are not in a dialog and 2) we need to present both the
- frame name and the sheet name. So we might as well return the
- sheet in place of the dialog so that the default code can do
- its thing.
- """
-
- if not self.isSpreadSheetCell(obj):
- return default.Script.findFrameAndDialog(self, obj)
-
- results = [None, None]
-
- parent = obj.parent
- while parent and (parent.parent != parent):
- if parent.getRole() == pyatspi.ROLE_FRAME:
- results[0] = parent
- if parent.getRole() == pyatspi.ROLE_TABLE:
- results[1] = parent
- parent = parent.parent
-
- return results
-
- def printHierarchy(self, root, ooi, indent="",
- onlyShowing=True, omitManaged=True):
- """Prints the accessible hierarchy of all children
-
- This is an override because of bugs in OOo's child/parent symmetry.
- See Script._getParent().
-
- Arguments:
- -indent: Indentation string
- -root: Accessible where to start
- -ooi: Accessible object of interest
- -onlyShowing: If True, only show children painted on the screen
- -omitManaged: If True, omit children that are managed descendants
- """
-
- if not root:
- return
-
- if root == ooi:
- print indent + "(*)", debug.getAccessibleDetails(root)
- else:
- print indent + "+-", debug.getAccessibleDetails(root)
-
- rootManagesDescendants = root.getState().contains( \
- pyatspi.STATE_MANAGES_DESCENDANTS)
-
- for child in root:
- if child == root:
- print indent + " " + "WARNING CHILD == PARENT!!!"
- elif not child:
- print indent + " " + "WARNING CHILD IS NONE!!!"
- elif self._getParent(child) != root:
- print indent + " " + "WARNING CHILD.PARENT != PARENT!!!"
- else:
- paint = (not onlyShowing) or (onlyShowing and \
- child.getState().contains(pyatspi.STATE_SHOWING))
- paint = paint \
- and ((not omitManaged) \
- or (omitManaged and not rootManagesDescendants))
-
- if paint:
- self.printHierarchy(child,
- ooi,
- indent + " ",
- onlyShowing,
- omitManaged)
-
- def _getParent(self, obj):
- """This method gets a node's parent will be doubly linked.
- See bugs:
- http://www.openoffice.org/issues/show_bug.cgi?id=78117
- http://bugzilla.gnome.org/show_bug.cgi?id=489490
- """
- parent = obj.parent
- if parent and parent.getRole() in (pyatspi.ROLE_ROOT_PANE,
- pyatspi.ROLE_DIALOG):
- app = obj.getApplication()
- for frame in app:
- if frame.childCount < 1 or \
- frame[0].getRole() not in (pyatspi.ROLE_ROOT_PANE,
- pyatspi.ROLE_OPTION_PANE):
- continue
- root_pane = frame[0]
- if obj in root_pane:
- return root_pane
- return parent
-
def presentTableInfo(self, oldFocus, newFocus):
"""Presents information relevant to a table that was just entered
(primarily) or exited.
@@ -836,16 +706,18 @@ class Script(default.Script):
Returns True if table info was presented.
"""
- oldAncestor = self.getAncestor(oldFocus,
- [pyatspi.ROLE_TABLE,
- pyatspi.ROLE_UNKNOWN,
- pyatspi.ROLE_DOCUMENT_FRAME],
- [pyatspi.ROLE_FRAME])
- newAncestor = self.getAncestor(newFocus,
- [pyatspi.ROLE_TABLE,
- pyatspi.ROLE_UNKNOWN,
- pyatspi.ROLE_DOCUMENT_FRAME],
- [pyatspi.ROLE_FRAME])
+ oldAncestor = self.utilities.ancestorWithRole(
+ oldFocus,
+ [pyatspi.ROLE_TABLE,
+ pyatspi.ROLE_UNKNOWN,
+ pyatspi.ROLE_DOCUMENT_FRAME],
+ [pyatspi.ROLE_FRAME])
+ newAncestor = self.utilities.ancestorWithRole(
+ newFocus,
+ [pyatspi.ROLE_TABLE,
+ pyatspi.ROLE_UNKNOWN,
+ pyatspi.ROLE_DOCUMENT_FRAME],
+ [pyatspi.ROLE_FRAME])
if not (oldAncestor and newAncestor):
# At least one of the objects not only is not in a table, but is
@@ -876,7 +748,7 @@ class Script(default.Script):
#
return False
- if not self.isSameObject(oldAncestor, newAncestor):
+ if not self.utilities.isSameObject(oldAncestor, newAncestor):
if oldTable:
# We've left a table. Announce this fact.
#
@@ -893,9 +765,8 @@ class Script(default.Script):
self.lastCell = [None, -1]
return True
- cell = self.getAncestor(newFocus,
- [pyatspi.ROLE_TABLE_CELL],
- [pyatspi.ROLE_TABLE])
+ cell = self.utilities.ancestorWithRole(
+ newFocus, [pyatspi.ROLE_TABLE_CELL], [pyatspi.ROLE_TABLE])
if not cell or self.lastCell[0] == cell:
# If we haven't found a cell, who knows what's going on? If
# the cell is the same as our last location, odds are that
@@ -921,7 +792,7 @@ class Script(default.Script):
offset = text.caretOffset
self.lastCell = [cell, offset]
- index = self.getCellIndex(cell)
+ index = self.utilities.cellIndex(cell)
column = newTable.getColumnAtIndex(index)
self.pointOfReference['lastColumn'] = column
row = newTable.getRowAtIndex(index)
@@ -948,7 +819,8 @@ class Script(default.Script):
if self.isSpreadSheetCell(orca_state.locusOfFocus):
try:
if self.inputLineForCell and self.inputLineForCell.queryText():
- inputLine = self.getText(self.inputLineForCell, 0, -1)
+ inputLine = \
+ self.utilities.substring(self.inputLineForCell, 0, -1)
if not inputLine:
# Translators: this is used to announce that the
# current input line in a spreadsheet is blank/empty.
@@ -982,7 +854,7 @@ class Script(default.Script):
parentTable = None
if parent and parentTable:
- index = self.getCellIndex(cell)
+ index = self.utilities.cellIndex(cell)
row = parentTable.getRowAtIndex(index)
return row
@@ -1007,7 +879,7 @@ class Script(default.Script):
parentTable = None
if parent and parentTable:
- index = self.getCellIndex(cell)
+ index = self.utilities.cellIndex(cell)
column = parentTable.getColumnAtIndex(index)
return column
@@ -1158,7 +1030,8 @@ class Script(default.Script):
wind up reading the word or not).
"""
- paragraph = self.findByRole(pane, pyatspi.ROLE_PARAGRAPH)
+ paragraph = self.utilities.descendantsWithRole(
+ pane, pyatspi.ROLE_PARAGRAPH)
# If there is not exactly one paragraph, this isn't the spellcheck
# dialog.
@@ -1207,7 +1080,7 @@ class Script(default.Script):
#
return False
- badWord = self.getText(paragraph[0], startOff, endOff - 1)
+ badWord = self.utilities.substring(paragraph[0], startOff, endOff - 1)
# Note that we often get two or more of these focus or property-change
# events each time there is a new misspelt word. We extract the
@@ -1228,7 +1101,7 @@ class Script(default.Script):
# Create a list of all the words found in the misspelt paragraph.
#
- text = self.getText(paragraph[0], 0, -1)
+ text = self.utilities.substring(paragraph[0], 0, -1)
allTokens = text.split()
self.speakMisspeltWord(allTokens, badWord)
@@ -1284,7 +1157,7 @@ class Script(default.Script):
voices = settings.voices
for i in range(startOffset, endOffset):
- if self.getLinkIndex(obj, i) >= 0:
+ if self.utilities.linkIndex(obj, i) >= 0:
voice = voices[settings.HYPERLINK_VOICE]
break
elif word.decode("UTF-8").isupper():
@@ -1334,7 +1207,7 @@ class Script(default.Script):
- label: the Setup dialog Label.
"""
- text = self.getDisplayedText(label)
+ text = self.utilities.displayedText(label)
if text:
speech.speak(text)
@@ -1345,7 +1218,8 @@ class Script(default.Script):
- panel: the Setup panel.
"""
- allLabels = self.findByRole(panel, pyatspi.ROLE_LABEL)
+ allLabels = self.utilities.descendantsWithRole(
+ panel, pyatspi.ROLE_LABEL)
for label in allLabels:
self.speakSetupLabel(label)
@@ -1375,7 +1249,7 @@ class Script(default.Script):
pyatspi.ROLE_OPTION_PANE,
pyatspi.ROLE_DIALOG,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
tmp = event.source.parent.parent
if tmp.name.startswith(panelName):
isPanel = True
@@ -1386,7 +1260,7 @@ class Script(default.Script):
pyatspi.ROLE_OPTION_PANE,
pyatspi.ROLE_DIALOG,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
if event.source.parent.name.startswith(panelName):
isPanel = True
@@ -1395,7 +1269,7 @@ class Script(default.Script):
pyatspi.ROLE_OPTION_PANE,
pyatspi.ROLE_DIALOG,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
if event.source.name.startswith(panelName):
isPanel = True
@@ -1500,7 +1374,7 @@ class Script(default.Script):
pyatspi.ROLE_PANEL,
pyatspi.ROLE_ROOT_PANE,
pyatspi.ROLE_FRAME]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
debug.println(self.debugLevel,
"StarOffice.locusOfFocusChanged - Writer: text paragraph.")
@@ -1522,7 +1396,7 @@ class Script(default.Script):
pyatspi.ROLE_OPTION_PANE,
pyatspi.ROLE_DIALOG,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
debug.println(self.debugLevel,
"StarOffice.locusOfFocusChanged - Setup dialog: " \
+ "License Agreement screen: Scroll Down button.")
@@ -1538,7 +1412,7 @@ class Script(default.Script):
pyatspi.ROLE_OPTION_PANE,
pyatspi.ROLE_DIALOG,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
debug.println(self.debugLevel,
"StarOffice.locusOfFocusChanged - Setup dialog: " \
+ "License Agreement screen: accept button.")
@@ -1552,7 +1426,7 @@ class Script(default.Script):
pyatspi.ROLE_OPTION_PANE,
pyatspi.ROLE_DIALOG,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
debug.println(self.debugLevel,
"StarOffice.locusOfFocusChanged - Setup dialog: " \
+ "Personal Data: Transfer Personal Data check box.")
@@ -1571,8 +1445,8 @@ class Script(default.Script):
# is using. We hate keying off stuff like this, but we're
# forced to in this case.
#
- if self.isDesiredFocusedItem(event.source, rolesList) and \
- event.source.name == _("First name"):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList) \
+ and event.source.name == _("First name"):
debug.println(self.debugLevel,
"StarOffice.locusOfFocusChanged - Setup dialog: " \
+ "User name: First Name text field.")
@@ -1581,7 +1455,8 @@ class Script(default.Script):
# (and not the ones that have LABEL_FOR relationships).
#
panel = event.source.parent
- allLabels = self.findByRole(panel, pyatspi.ROLE_LABEL)
+ allLabels = self.utilities.descendantsWithRole(
+ panel, pyatspi.ROLE_LABEL)
for label in allLabels:
relations = label.getRelationSet()
hasLabelFor = False
@@ -1599,7 +1474,7 @@ class Script(default.Script):
pyatspi.ROLE_OPTION_PANE,
pyatspi.ROLE_DIALOG,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
debug.println(self.debugLevel,
"StarOffice.locusOfFocusChanged - Setup dialog: " \
+ "Registration: Register Now radio button.")
@@ -1616,7 +1491,7 @@ class Script(default.Script):
pyatspi.ROLE_ROOT_PANE,
pyatspi.ROLE_FRAME,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
debug.println(self.debugLevel, "StarOffice.locusOfFocusChanged - " \
+ "Calc: cell editor.")
return
@@ -1635,7 +1510,7 @@ class Script(default.Script):
pyatspi.ROLE_FRAME,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList) \
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList) \
and (not event.source.name or len(event.source.name) == 0):
debug.println(self.debugLevel, "StarOffice.locusOfFocusChanged - " \
+ "Calc: name box.")
@@ -1669,7 +1544,7 @@ class Script(default.Script):
except:
pass
else:
- index = self.getCellIndex(newLocusOfFocus)
+ index = self.utilities.cellIndex(newLocusOfFocus)
column = table.getColumnAtIndex(index)
self.pointOfReference['lastColumn'] = column
row = table.getRowAtIndex(index)
@@ -1686,7 +1561,7 @@ class Script(default.Script):
pyatspi.ROLE_FRAME,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
debug.println(self.debugLevel, "soffice.locusOfFocusChanged - " \
+ "Impress: scroll pane.")
@@ -1713,11 +1588,12 @@ class Script(default.Script):
pyatspi.ROLE_ROOT_PANE,
pyatspi.ROLE_FRAME,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
default.Script.locusOfFocusChanged(self, event,
oldLocusOfFocus, newLocusOfFocus)
for child in event.source:
- speech.speak(self.getText(child, 0, -1), None, False)
+ speech.speak(self.utilities.substring(child, 0, -1),
+ None, False)
return
# Combo boxes in OOo typically have two children: a text object
@@ -1731,7 +1607,7 @@ class Script(default.Script):
if newLocusOfFocus and oldLocusOfFocus \
and newLocusOfFocus.getRole() == pyatspi.ROLE_LIST \
and newLocusOfFocus.parent.getRole() == pyatspi.ROLE_COMBO_BOX \
- and not self.isSameObject(newLocusOfFocus.parent,
+ and not self.utilities.isSameObject(newLocusOfFocus.parent,
oldLocusOfFocus.parent):
# If the combo box contents cannot be edited, just present the
@@ -1770,11 +1646,12 @@ class Script(default.Script):
debug.println(self.debugLevel,
"StarOffice.onWindowActivated - Setup dialog: Welcome screen.")
- allPanels = self.findByRole(event.source.parent,
- pyatspi.ROLE_PANEL)
+ allPanels = self.utilities.descendantsWithRole(
+ event.source.parent, pyatspi.ROLE_PANEL)
for panel in allPanels:
if not panel.name:
- allLabels = self.findByRole(panel, pyatspi.ROLE_LABEL)
+ allLabels = self.utilities.descendantsWithRole(
+ panel, pyatspi.ROLE_LABEL)
for label in allLabels:
self.speakSetupLabel(label)
else:
@@ -1820,8 +1697,9 @@ class Script(default.Script):
rolesList = [pyatspi.ROLE_OPTION_PANE, \
pyatspi.ROLE_DIALOG, \
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList) \
- and self.isSameObject(event.source.parent, orca_state.activeWindow):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList) \
+ and self.utilities.isSameObject(
+ event.source.parent, orca_state.activeWindow):
self.readMisspeltWord(event, event.source)
default.Script.onNameChanged(self, event)
@@ -1850,7 +1728,7 @@ class Script(default.Script):
pyatspi.ROLE_ROOT_PANE,
pyatspi.ROLE_FRAME,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
debug.println(self.debugLevel, "StarOffice.onFocus - " \
+ "Calc: Name combo box.")
orca.setLocusOfFocus(event, event.source)
@@ -1861,7 +1739,8 @@ class Script(default.Script):
#
if event.source.getRole() == pyatspi.ROLE_LIST \
and orca_state.locusOfFocus \
- and self.isSameObject(orca_state.locusOfFocus.parent, event.source):
+ and self.utilities.isSameObject(
+ orca_state.locusOfFocus.parent, event.source):
return
default.Script.onFocus(self, event)
@@ -1890,19 +1769,19 @@ class Script(default.Script):
pyatspi.ROLE_PANEL,
pyatspi.ROLE_PANEL,
pyatspi.ROLE_LIST_ITEM]
- if self.isDesiredFocusedItem(event.source, rolesList) \
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList) \
and event.any_data:
handleEvent = True
# The style list in the Formatting toolbar also lacks state
# focused.
#
- elif event.any_data and self.getAncestor(event.source,
- [pyatspi.ROLE_TOOL_BAR],
- [pyatspi.ROLE_FRAME]):
+ elif event.any_data and self.utilities.ancestorWithRole(
+ event.source, [pyatspi.ROLE_TOOL_BAR], [pyatspi.ROLE_FRAME]):
handleEvent = True
- elif self.isSameObject(orca_state.locusOfFocus, event.source.parent) \
+ elif self.utilities.isSameObject(
+ orca_state.locusOfFocus, event.source.parent) \
and event.source.getRole() == pyatspi.ROLE_LIST \
and orca_state.locusOfFocus.getRole() == pyatspi.ROLE_COMBO_BOX:
# Combo boxes which have been explicitly given focus by the user
@@ -1921,7 +1800,8 @@ class Script(default.Script):
presentEvent = False
if orca_state.locusOfFocus \
- and self.isSameObject(orca_state.locusOfFocus, event.any_data):
+ and self.utilities.isSameObject(
+ orca_state.locusOfFocus, event.any_data):
# We're already on the new item. If we (or the default script)
# presents it, the speech.stop() will cause us to interrupt the
# presentation we're probably about to make due to an earlier
@@ -2033,7 +1913,7 @@ class Script(default.Script):
pyatspi.ROLE_PANEL,
pyatspi.ROLE_ROOT_PANE,
pyatspi.ROLE_FRAME]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList):
orca.setLocusOfFocus(
event, event.source, notifyPresentationManager=False)
if event.source != self.currentParagraph:
@@ -2087,7 +1967,8 @@ class Script(default.Script):
pyatspi.ROLE_ROOT_PANE,
pyatspi.ROLE_FRAME,
pyatspi.ROLE_APPLICATION]
- if self.isDesiredFocusedItem(event.source, rolesList):
+ if self.utilities.hasMatchingHierarchy(event.source, rolesList) \
+ and orca_state.locusOfFocus:
if orca_state.locusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL:
cell = orca_state.locusOfFocus
@@ -2101,12 +1982,12 @@ class Script(default.Script):
try:
if cell.queryText():
- cellText = self.getText(cell, 0, -1)
+ cellText = self.utilities.substring(cell, 0, -1)
if cellText and len(cellText):
try:
if self.inputLineForCell and \
self.inputLineForCell.queryText():
- inputLine = self.getText( \
+ inputLine = self.utilities.substring( \
self.inputLineForCell, 0, -1)
if inputLine and (len(inputLine) > 1) \
and (inputLine[0] == "="):
@@ -2129,34 +2010,6 @@ class Script(default.Script):
default.Script.onSelectionChanged(self, event)
- def getText(self, obj, startOffset, endOffset):
- """Returns the substring of the given object's text specialization.
-
- NOTE: This is here to handle the problematic implementation of
- getText by OpenOffice. See the bug discussion at:
-
- http://bugzilla.gnome.org/show_bug.cgi?id=356425)
-
- Once the OpenOffice issue has been resolved, this method probably
- should be removed.
-
- Arguments:
- - obj: an accessible supporting the accessible text specialization
- - startOffset: the starting character position
- - endOffset: the ending character position
- """
-
- text = obj.queryText().getText(0, -1).decode("UTF-8")
- if startOffset >= len(text):
- startOffset = len(text) - 1
- if endOffset == -1:
- endOffset = len(text)
- elif startOffset >= endOffset:
- endOffset = startOffset + 1
- string = text[max(0, startOffset):min(len(text), endOffset)]
- string = string.encode("UTF-8")
- return string
-
def speakCellName(self, name):
"""Speaks the given cell name.
@@ -2212,15 +2065,15 @@ class Script(default.Script):
pyatspi.ROLE_FRAME,
pyatspi.ROLE_APPLICATION]
if settings.enableEchoByWord and \
- (self.isDesiredFocusedItem(event.source, rolesList) or
- self.isDesiredFocusedItem(event.source, rolesList1)):
+ (self.utilities.hasMatchingHierarchy(event.source, rolesList) or
+ self.utilities.hasMatchingHierarchy(event.source, rolesList1)):
if isinstance(orca_state.lastInputEvent,
input_event.KeyboardEvent):
keyString = orca_state.lastNonModifierKeyEvent.event_string
focusRole = orca_state.locusOfFocus.getRole()
if focusRole != pyatspi.ROLE_UNKNOWN and \
keyString == "Return":
- result = self.getText(event.source, 0, -1)
+ result = self.utilities.substring(event.source, 0, -1)
line = result.decode("UTF-8")
self.echoPreviousWord(event.source, len(line))
return
@@ -2274,7 +2127,7 @@ class Script(default.Script):
or (event_string == "Up" and (eventIndex - paraIndex >= 0)):
return
- result = self.getText(event.source, 0, -1)
+ result = self.utilities.substring(event.source, 0, -1)
textToSpeak = result.decode("UTF-8")
self._speakWriterText(event, textToSpeak)
self.displayBrailleForObject(event.source)
@@ -2286,9 +2139,8 @@ class Script(default.Script):
# to the event not being from the locusOfFocus.
#
if event.source.getRole() == pyatspi.ROLE_TEXT \
- and self.getAncestor(event.source,
- [pyatspi.ROLE_TOOL_BAR],
- [pyatspi.ROLE_FRAME]):
+ and self.utilities.ancestorWithRole(
+ event.source, [pyatspi.ROLE_TOOL_BAR], [pyatspi.ROLE_FRAME]):
orca.setLocusOfFocus(event, event.source, False)
default.Script.onCaretMoved(self, event)
@@ -2342,77 +2194,6 @@ class Script(default.Script):
else:
default.Script.onTextInserted(self, event)
- def isWordMisspelled(self, obj, offset):
- """Identifies if the current word is flagged as misspelled by the
- application.
-
- Arguments:
- - obj: An accessible which implements the accessible text interface.
- - offset: Offset in the accessible's text for which to retrieve the
- attributes.
-
- Returns True if the word is flagged as misspelled.
- """
-
- attributes, start, end = self.getTextAttributes(obj, offset, True)
- error = attributes.get("text-spelling")
-
- return error == "misspelled"
-
- def getTextAttributes(self, acc, offset, get_defaults=False):
- """Get the text attributes run for a given offset in a given accessible
-
- Arguments:
- - acc: An accessible.
- - offset: Offset in the accessible's text for which to retrieve the
- attributes.
- - get_defaults: Get the default attributes as well as the unique ones.
- Default is True
-
- Returns a dictionary of attributes, a start offset where the attributes
- begin, and an end offset. Returns ({}, 0, 0) if the accessible does not
- supprt the text attribute.
- """
- rv, start, end = \
- default.Script.getTextAttributes(self, acc, offset, get_defaults)
-
- # If there are no text attributes associated with the text at a
- # given offset, we might get some seriously bogus offsets, in
- # particular, an extremely large start offset and an extremely
- # large, but negative end offset. As a result, any text attributes
- # which are present on the line after the specified offset will
- # not be indicated by braille.py's getAttributeMask. Therefore,
- # we'll set the start offset to the character being examined,
- # and the end offset to the next character.
- #
- start = min(start, offset)
- if end < 0:
- debug.println(debug.LEVEL_WARNING,
- "soffice.script.py:getTextAttributes: detected a bogus " +
- "end offset. Start offset: %s, end offset: %s" % (start, end))
- end = offset + 1
- else:
- end -= 1
-
- return rv, start, end
-
- def getDisplayedText(self, obj):
- """Returns the text being displayed for an object. Overridden here
- because OpenOffice uses symbols (e.g. ">>" for buttons but exposes
- more useful information via the accessible's name.
-
- Arguments:
- - obj: the object
-
- Returns the text being displayed for an object or None if there isn't
- any text being shown.
- """
-
- if obj.getRole() == pyatspi.ROLE_PUSH_BUTTON and obj.name:
- return obj.name
- else:
- return default.Script.getDisplayedText(self, obj)
-
def getTextLineAtCaret(self, obj, offset=None):
"""Gets the line of text where the caret is. Overridden here to
handle combo boxes who have a text object with a caret offset
@@ -2447,33 +2228,3 @@ class Script(default.Script):
return [content.encode("UTF-8"), 0, startOffset]
return default.Script.getTextLineAtCaret(self, obj, offset)
-
- def isFunctionalDialog(self, obj):
- """Returns true if the window is functioning as a dialog."""
-
- # The OOo Navigator window looks like a dialog, acts like a
- # dialog, and loses focus requiring the user to know that it's
- # there and needs Alt+F6ing into. But officially it's a normal
- # window.
-
- # There doesn't seem to be (an efficient) top-down equivalent
- # of isDesiredFocusedItem(). But OOo documents have root panes;
- # this thing does not.
- #
- rolesList = [pyatspi.ROLE_FRAME,
- pyatspi.ROLE_PANEL,
- pyatspi.ROLE_PANEL,
- pyatspi.ROLE_TOOL_BAR,
- pyatspi.ROLE_PUSH_BUTTON]
-
- if obj.getRole() != rolesList[0]:
- # We might be looking at the child.
- #
- rolesList.pop(0)
-
- while obj and obj.childCount and len(rolesList):
- if obj.getRole() != rolesList.pop(0):
- return False
- obj = obj[0]
-
- return True
diff --git a/src/orca/scripts/apps/soffice/script_utilities.py b/src/orca/scripts/apps/soffice/script_utilities.py
new file mode 100644
index 0000000..81c7cff
--- /dev/null
+++ b/src/orca/scripts/apps/soffice/script_utilities.py
@@ -0,0 +1,267 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Commonly-required utility methods needed by -- and potentially
+ customized by -- application and toolkit scripts. They have
+ been pulled out from the scripts because certain scripts had
+ gotten way too large as a result of including these methods."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__ = "LGPL"
+
+import pyatspi
+
+import orca.debug as debug
+import orca.script_utilities as script_utilities
+
+#############################################################################
+# #
+# Utilities #
+# #
+#############################################################################
+
+class Utilities(script_utilities.Utilities):
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ script_utilities.Utilities.__init__(self, script)
+
+ #########################################################################
+ # #
+ # Utilities for finding, identifying, and comparing accessibles #
+ # #
+ #########################################################################
+
+ def displayedText(self, obj):
+ """Returns the text being displayed for an object. Overridden here
+ because OpenOffice uses symbols (e.g. ">>" for buttons but exposes
+ more useful information via the accessible's name.
+
+ Arguments:
+ - obj: the object
+
+ Returns the text being displayed for an object or None if there isn't
+ any text being shown.
+ """
+
+ if obj.getRole() == pyatspi.ROLE_PUSH_BUTTON and obj.name:
+ return obj.name
+ else:
+ return script_utilities.Utilities.displayedText(self, obj)
+
+ def isReadOnlyTextArea(self, obj):
+ """Returns True if obj is a text entry area that is read only."""
+
+ if not obj.getRole() == pyatspi.ROLE_TEXT:
+ return False
+
+ state = obj.getState()
+ readOnly = state.contains(pyatspi.STATE_FOCUSABLE) \
+ and not state.contains(pyatspi.STATE_EDITABLE)
+ debug.println(debug.LEVEL_ALL,
+ "soffice - isReadOnlyTextArea=%s for %s" \
+ % (readOnly, debug.getAccessibleDetails(obj)))
+
+ return readOnly
+
+ def frameAndDialog(self, obj):
+ """Returns the frame and (possibly) the dialog containing
+ the object. Overridden here for presentation of the title
+ bar information: If the locusOfFocus is a spreadsheet cell,
+ 1) we are not in a dialog and 2) we need to present both the
+ frame name and the sheet name. So we might as well return the
+ sheet in place of the dialog so that the default code can do
+ its thing.
+ """
+
+ if not self._script.isSpreadSheetCell(obj):
+ return script_utilities.Utilities.frameAndDialog(self, obj)
+
+ results = [None, None]
+
+ parent = obj.parent
+ while parent and (parent.parent != parent):
+ if parent.getRole() == pyatspi.ROLE_FRAME:
+ results[0] = parent
+ if parent.getRole() == pyatspi.ROLE_TABLE:
+ results[1] = parent
+ parent = parent.parent
+
+ return results
+
+ def isFunctionalDialog(self, obj):
+ """Returns true if the window is functioning as a dialog."""
+
+ # The OOo Navigator window looks like a dialog, acts like a
+ # dialog, and loses focus requiring the user to know that it's
+ # there and needs Alt+F6ing into. But officially it's a normal
+ # window.
+
+ # There doesn't seem to be (an efficient) top-down equivalent
+ # of utilities.hasMatchingHierarchy(). But OOo documents have
+ # root panes; this thing does not.
+ #
+ rolesList = [pyatspi.ROLE_FRAME,
+ pyatspi.ROLE_PANEL,
+ pyatspi.ROLE_PANEL,
+ pyatspi.ROLE_TOOL_BAR,
+ pyatspi.ROLE_PUSH_BUTTON]
+
+ if obj.getRole() != rolesList[0]:
+ # We might be looking at the child.
+ #
+ rolesList.pop(0)
+
+ while obj and obj.childCount and len(rolesList):
+ if obj.getRole() != rolesList.pop(0):
+ return False
+ obj = obj[0]
+
+ return True
+
+ def validParent(self, obj):
+ """Returns the first valid parent/ancestor of obj. We need to do
+ this in some applications and toolkits due to bogus hierarchies.
+
+ See bugs:
+ http://www.openoffice.org/issues/show_bug.cgi?id=78117
+ http://bugzilla.gnome.org/show_bug.cgi?id=489490
+
+ Arguments:
+ - obj: the Accessible object
+ """
+
+ parent = obj.parent
+ if parent and parent.getRole() in (pyatspi.ROLE_ROOT_PANE,
+ pyatspi.ROLE_DIALOG):
+ app = obj.getApplication()
+ for frame in app:
+ if frame.childCount < 1 \
+ or frame[0].getRole() not in (pyatspi.ROLE_ROOT_PANE,
+ pyatspi.ROLE_OPTION_PANE):
+ continue
+
+ root_pane = frame[0]
+ if obj in root_pane:
+ return root_pane
+
+ return parent
+
+ #########################################################################
+ # #
+ # Utilities for working with the accessible text interface #
+ # #
+ #########################################################################
+
+ def isWordMisspelled(self, obj, offset):
+ """Identifies if the current word is flagged as misspelled by the
+ application.
+
+ Arguments:
+ - obj: An accessible which implements the accessible text interface.
+ - offset: Offset in the accessible's text for which to retrieve the
+ attributes.
+
+ Returns True if the word is flagged as misspelled.
+ """
+
+ attributes, start, end = self.textAttributes(obj, offset, True)
+ error = attributes.get("text-spelling")
+
+ return error == "misspelled"
+
+ def substring(self, obj, startOffset, endOffset):
+ """Returns the substring of the given object's text specialization.
+
+ NOTE: This is here to handle the problematic implementation of
+ getText by OpenOffice. See the bug discussion at:
+
+ http://bugzilla.gnome.org/show_bug.cgi?id=356425)
+
+ Once the OpenOffice issue has been resolved, this method probably
+ should be removed.
+
+ Arguments:
+ - obj: an accessible supporting the accessible text specialization
+ - startOffset: the starting character position
+ - endOffset: the ending character position
+ """
+
+ text = obj.queryText().getText(0, -1).decode("UTF-8")
+ if startOffset >= len(text):
+ startOffset = len(text) - 1
+ if endOffset == -1:
+ endOffset = len(text)
+ elif startOffset >= endOffset:
+ endOffset = startOffset + 1
+ string = text[max(0, startOffset):min(len(text), endOffset)]
+ string = string.encode("UTF-8")
+
+ return string
+
+ def textAttributes(self, acc, offset, get_defaults=False):
+ """Get the text attributes run for a given offset in a given accessible
+
+ Arguments:
+ - acc: An accessible.
+ - offset: Offset in the accessible's text for which to retrieve the
+ attributes.
+ - get_defaults: Get the default attributes as well as the unique ones.
+ Default is True
+
+ Returns a dictionary of attributes, a start offset where the attributes
+ begin, and an end offset. Returns ({}, 0, 0) if the accessible does not
+ supprt the text attribute.
+ """
+ rv, start, end = script_utilities.Utilities.\
+ textAttributes(self, acc, offset, get_defaults)
+
+ # If there are no text attributes associated with the text at a
+ # given offset, we might get some seriously bogus offsets, in
+ # particular, an extremely large start offset and an extremely
+ # large, but negative end offset. As a result, any text attributes
+ # which are present on the line after the specified offset will
+ # not be indicated by braille.py's getAttributeMask. Therefore,
+ # we'll set the start offset to the character being examined,
+ # and the end offset to the next character.
+ #
+ start = min(start, offset)
+ if end < 0:
+ debug.println(debug.LEVEL_WARNING,
+ "soffice.script.py:getTextAttributes: detected a bogus " +
+ "end offset. Start offset: %s, end offset: %s" % (start, end))
+ end = offset + 1
+ else:
+ end -= 1
+
+ return rv, start, end
+
+ #########################################################################
+ # #
+ # Miscellaneous Utilities #
+ # #
+ #########################################################################
diff --git a/src/orca/scripts/apps/soffice/speech_generator.py b/src/orca/scripts/apps/soffice/speech_generator.py
index 578ff37..1be65f8 100644
--- a/src/orca/scripts/apps/soffice/speech_generator.py
+++ b/src/orca/scripts/apps/soffice/speech_generator.py
@@ -50,9 +50,8 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
override = \
role == "text frame" \
or (role == pyatspi.ROLE_PARAGRAPH \
- and self._script.getAncestor(obj,
- [pyatspi.ROLE_DIALOG],
- [pyatspi.ROLE_APPLICATION]))
+ and self._script.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_DIALOG], [pyatspi.ROLE_APPLICATION]))
return override
def _generateRoleName(self, obj, **args):
@@ -93,14 +92,14 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
def _generateLabel(self, obj, **args):
"""Returns the label for an object as an array of strings (and
possibly voice and audio specifications). The label is
- determined by the getDisplayedLabel of the script, and an
- empty array will be returned if no label can be found.
+ determined by the displayedLabel method of the script utility,
+ and an empty array will be returned if no label can be found.
"""
result = []
override = self.__overrideParagraph(obj, **args)
- label = self._script.getDisplayedLabel(obj) or ""
+ label = self._script.utilities.displayedLabel(obj) or ""
if not label and override:
- label = self._script.getDisplayedLabel(obj.parent) or ""
+ label = self._script.utilities.displayedLabel(obj.parent) or ""
result.append(label.strip())
return result
@@ -143,7 +142,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
# and the displayed text, with punctuation added. Try to spot
# this and, if found, ignore the description.
#
- text = self._script.getDisplayedText(obj) or ""
+ text = self._script.utilities.displayedText(obj) or ""
desc = obj.description.replace(text, "")
for item in obj.name.split():
desc = desc.replace(item, "")
@@ -188,7 +187,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
except:
pass
else:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
rowIndex = table.getRowAtIndex(index)
if rowIndex >= 0 \
and table in self._script.dynamicRowHeaders:
@@ -200,11 +199,11 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
headerText = None
if header.childCount > 0:
for child in header:
- text = self._script.getText(child, 0, -1)
+ text = self._script.utilities.substring(child, 0, -1)
if text:
result.append(text)
elif headerText:
- text = self._script.getText(header, 0, -1)
+ text = self._script.utilities.substring(header, 0, -1)
if text:
result.append(text)
return result
@@ -220,7 +219,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
parentTable = parent.queryTable()
except NotImplementedError:
parentTable = None
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
if "lastRow" in self._script.pointOfReference and \
self._script.pointOfReference["lastRow"] != \
parentTable.getRowAtIndex(index):
@@ -233,11 +232,11 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
headerText = None
if header.childCount > 0:
for child in header:
- text = self._script.getText(child, 0, -1)
+ text = self._script.utilities.substring(child, 0, -1)
if text:
result.append(text)
elif headerText:
- text = self._script.getText(header, 0, -1)
+ text = self._script.utilities.substring(header, 0, -1)
if text:
result.append(text)
return result
@@ -255,7 +254,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
except:
pass
else:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
columnIndex = table.getColumnAtIndex(index)
if columnIndex >= 0 \
and table in self._script.dynamicColumnHeaders:
@@ -267,11 +266,11 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
headerText = None
if header.childCount > 0:
for child in header:
- text = self._script.getText(child, 0, -1)
+ text = self._script.utilities.substring(child, 0, -1)
if text:
result.append(text)
elif headerText:
- text = self._script.getText(header, 0, -1)
+ text = self._script.utilities.substring(header, 0, -1)
if text:
result.append(text)
return result
@@ -287,7 +286,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
parentTable = parent.queryTable()
except NotImplementedError:
parentTable = None
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
if "lastColumn" in self._script.pointOfReference and \
self._script.pointOfReference["lastColumn"] != \
parentTable.getColumnAtIndex(index):
@@ -300,11 +299,11 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
headerText = None
if header.childCount > 0:
for child in header:
- text = self._script.getText(child, 0, -1)
+ text = self._script.utilities.substring(child, 0, -1)
if text:
result.append(text)
elif headerText:
- text = self._script.getText(header, 0, -1)
+ text = self._script.utilities.substring(header, 0, -1)
if text:
result.append(text)
return result
@@ -320,7 +319,8 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
result = []
try:
text = obj.queryText()
- objectText = self._script.getText(obj, 0, -1).decode("UTF-8")
+ objectText = \
+ self._script.utilities.substring(obj, 0, -1).decode("UTF-8")
extents = obj.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
except NotImplementedError:
pass
@@ -350,7 +350,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
self._script.locateInputLine(obj)
try:
if obj.queryText():
- objectText = self._script.getText(obj, 0, -1)
+ objectText = self._script.utilities.substring(obj, 0, -1)
if (not script_settings.speakSpreadsheetCoordinates \
or args.get('formatType', 'unfocused') == 'basicWhereAmI') \
and len(objectText) == 0:
@@ -429,7 +429,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
if settings.readTableCellRow:
parent = obj.parent
parentTable = parent.queryTable()
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
row = parentTable.getRowAtIndex(index)
column = parentTable.getColumnAtIndex(index)
# This is an indication of whether we should speak all the
diff --git a/src/orca/scripts/apps/soffice/structural_navigation.py b/src/orca/scripts/apps/soffice/structural_navigation.py
index 75ed8b5..063d450 100644
--- a/src/orca/scripts/apps/soffice/structural_navigation.py
+++ b/src/orca/scripts/apps/soffice/structural_navigation.py
@@ -108,7 +108,7 @@ class StructuralNavigation(structural_navigation.StructuralNavigation):
blank = self._isBlankCell(cell)
if not blank:
for child in cell:
- speech.speak(self._script.getDisplayedText(child))
+ speech.speak(self._script.utilities.displayedText(child))
else:
# Translators: "blank" is a short word to mean the
# user has navigated to an empty line.
diff --git a/src/orca/scripts/apps/yelp/Makefile.am b/src/orca/scripts/apps/yelp/Makefile.am
index d748393..6af5e0a 100644
--- a/src/orca/scripts/apps/yelp/Makefile.am
+++ b/src/orca/scripts/apps/yelp/Makefile.am
@@ -3,7 +3,8 @@ orca_pathdir=$(pyexecdir)
orca_python_PYTHON = \
__init__.py \
script.py \
- script_settings.py
+ script_settings.py \
+ script_utilities.py
orca_pythondir=$(pyexecdir)/orca/scripts/apps/yelp
diff --git a/src/orca/scripts/apps/yelp/script.py b/src/orca/scripts/apps/yelp/script.py
index ae94981..30153c1 100644
--- a/src/orca/scripts/apps/yelp/script.py
+++ b/src/orca/scripts/apps/yelp/script.py
@@ -29,13 +29,13 @@ import gtk
import pyatspi
import orca.orca as orca
-import orca.orca_state as orca_state
import orca.settings as settings
import orca.speech as speech
import orca.scripts.toolkits.Gecko as Gecko
import script_settings
+from script_utilities import Utilities
class Script(Gecko.Script):
@@ -56,10 +56,15 @@ class Script(Gecko.Script):
# When the user presses Escape to leave the Find tool bar, we do not
# seem to get any events for the document frame reclaiming focus. If
- # we try to get the caret context, getDocumentFrame() returns None.
+ # we try to get the caret context, documentFrame() returns None.
# Store a copy of the context so that we can return it.
#
- self._lastFindContext = [None, -1]
+ self.lastFindContext = [None, -1]
+
+ def getUtilities(self):
+ """Returns the utilites for this script."""
+
+ return Utilities(self)
def getAppPreferencesGUI(self):
"""Return a GtkVBox contain the application unique configuration
@@ -95,54 +100,6 @@ class Script(Gecko.Script):
prefs.writelines("%s.grabFocusOnAncestor = %s\n" % (prefix, value))
script_settings.grabFocusOnAncestor = value
- def inFindToolbar(self, obj=None):
- """Returns True if the given object is in the Find toolbar.
-
- Arguments:
- - obj: an accessible object
- """
-
- if not obj:
- obj = orca_state.locusOfFocus
-
- if obj and obj.getRole() == pyatspi.ROLE_TEXT \
- and obj.parent.getRole() == pyatspi.ROLE_FILLER:
- return True
-
- return False
-
- def getDocumentFrame(self):
- """Returns the document frame that holds the content being shown."""
-
- obj = orca_state.locusOfFocus
- #print "getDocumentFrame", obj
-
- if not obj:
- return None
-
- role = obj.getRole()
- if role == pyatspi.ROLE_DOCUMENT_FRAME:
- # We caught a lucky break.
- #
- return obj
- elif role == pyatspi.ROLE_FRAME:
- # The window was just activated. Do not look from the top down;
- # it will cause the yelp hierarchy to become crazy, resulting in
- # all future events having an empty name for the application.
- # See bug 356041 for more information.
- #
- return None
- else:
- if self.inFindToolbar():
- obj = self._lastFindContext[0]
-
- # We might be in some content. In this case, look up.
- #
- return self.getAncestor(obj,
- [pyatspi.ROLE_DOCUMENT_FRAME,
- pyatspi.ROLE_EMBEDDED],
- [pyatspi.ROLE_FRAME])
-
def onCaretMoved(self, event):
"""Called whenever the caret moves.
@@ -150,8 +107,9 @@ class Script(Gecko.Script):
- event: the Event
"""
- if self.inFindToolbar() and not self.inFindToolbar(event.source):
- self._lastFindContext = [event.source, event.detail1]
+ if self.utilities.utilities.inFindToolbar() \
+ and not self.utilities.utilities.inFindToolbar(event.source):
+ self.lastFindContext = [event.source, event.detail1]
# Unlike the unpredictable wild, wild web, odds are good that a
# caret-moved event from document content in Yelp is valid. But
@@ -160,7 +118,7 @@ class Script(Gecko.Script):
# Rather than risk breaking access to web content, we'll just set
# the locusOfFocus here before sending this event on.
#
- elif self.inDocumentContent(event.source):
+ elif self.utilities.inDocumentContent(event.source):
obj = event.source
characterOffset = event.detail1
@@ -168,7 +126,7 @@ class Script(Gecko.Script):
# something more meaningful.
#
while obj and obj.getRole() == pyatspi.ROLE_LINK \
- and not self.queryNonEmptyText(obj):
+ and not self.utilities.queryNonEmptyText(obj):
[obj, characterOffset] = \
self.findNextCaretInOrder(obj, characterOffset)
@@ -179,7 +137,7 @@ class Script(Gecko.Script):
# handled by onStateChanged(). Therefore, we need to notify the
# presentation managers if this event is not for an empty anchor.
#
- notify = self.isSameObject(event.source, obj)
+ notify = self.utilities.isSameObject(event.source, obj)
orca.setLocusOfFocus(
event, obj, notifyPresentationManager=notify)
self.setCaretPosition(obj, characterOffset)
@@ -208,9 +166,10 @@ class Script(Gecko.Script):
# button. For now, let's stop that from happening.
#
if event.type.startswith("object:state-changed:showing") \
- and not event.detail1 and self.inFindToolbar(event.source):
- [obj, characterOffset] = self._lastFindContext
- self._lastFindContext = [None, -1]
+ and not event.detail1 \
+ and self.utilities.utilities.inFindToolbar(event.source):
+ [obj, characterOffset] = self.lastFindContext
+ self.lastFindContext = [None, -1]
self.setCaretPosition(obj, characterOffset)
if event.type.startswith("object:state-changed:busy") \
@@ -228,7 +187,7 @@ class Script(Gecko.Script):
# find something more meaningful and set the caret there.
#
while obj and obj.getRole() == pyatspi.ROLE_LINK \
- and not self.queryNonEmptyText(obj):
+ and not self.utilities.queryNonEmptyText(obj):
[obj, characterOffset] = \
self.findNextCaretInOrder(obj, characterOffset)
diff --git a/src/orca/scripts/apps/yelp/script_utilities.py b/src/orca/scripts/apps/yelp/script_utilities.py
new file mode 100644
index 0000000..9a38de4
--- /dev/null
+++ b/src/orca/scripts/apps/yelp/script_utilities.py
@@ -0,0 +1,118 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Commonly-required utility methods needed by -- and potentially
+ customized by -- application and toolkit scripts. They have
+ been pulled out from the scripts because certain scripts had
+ gotten way too large as a result of including these methods."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__ = "LGPL"
+
+import pyatspi
+
+import orca.orca_state as orca_state
+
+import orca.scripts.toolkits.Gecko as Gecko
+
+#############################################################################
+# #
+# Utilities #
+# #
+#############################################################################
+
+class Utilities(Gecko.Utilities):
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ Gecko.Utilities.__init__(self, script)
+
+ #########################################################################
+ # #
+ # Utilities for finding, identifying, and comparing accessibles #
+ # #
+ #########################################################################
+
+ def documentFrame(self):
+ """Returns the document frame that holds the content being shown."""
+
+ obj = orca_state.locusOfFocus
+ #print "documentFrame", obj
+
+ if not obj:
+ return None
+
+ role = obj.getRole()
+ if role == pyatspi.ROLE_DOCUMENT_FRAME:
+ # We caught a lucky break.
+ #
+ return obj
+ elif role == pyatspi.ROLE_FRAME:
+ # The window was just activated. Do not look from the top down;
+ # it will cause the yelp hierarchy to become crazy, resulting in
+ # all future events having an empty name for the application.
+ # See bug 356041 for more information.
+ #
+ return None
+ else:
+ if self.inFindToolbar():
+ obj = self._script.lastFindContext[0]
+
+ # We might be in some content. In this case, look up.
+ #
+ return self.ancestorWithRole(obj,
+ [pyatspi.ROLE_DOCUMENT_FRAME,
+ pyatspi.ROLE_EMBEDDED],
+ [pyatspi.ROLE_FRAME])
+
+ def inFindToolbar(self, obj=None):
+ """Returns True if the given object is in the Find toolbar.
+
+ Arguments:
+ - obj: an accessible object
+ """
+
+ if not obj:
+ obj = orca_state.locusOfFocus
+
+ if obj and obj.getRole() == pyatspi.ROLE_TEXT \
+ and obj.parent.getRole() == pyatspi.ROLE_FILLER:
+ return True
+
+ return False
+
+ #########################################################################
+ # #
+ # Utilities for working with the accessible text interface #
+ # #
+ #########################################################################
+
+ #########################################################################
+ # #
+ # Miscellaneous Utilities #
+ # #
+ #########################################################################
diff --git a/src/orca/scripts/toolkits/Gecko/Makefile.am b/src/orca/scripts/toolkits/Gecko/Makefile.am
index 44e9962..9ab1ca4 100644
--- a/src/orca/scripts/toolkits/Gecko/Makefile.am
+++ b/src/orca/scripts/toolkits/Gecko/Makefile.am
@@ -7,6 +7,7 @@ orca_python_PYTHON = \
formatting.py \
script.py \
script_settings.py \
+ script_utilities.py \
speech_generator.py \
structural_navigation.py
diff --git a/src/orca/scripts/toolkits/Gecko/__init__.py b/src/orca/scripts/toolkits/Gecko/__init__.py
index 80e9b2c..90052c1 100644
--- a/src/orca/scripts/toolkits/Gecko/__init__.py
+++ b/src/orca/scripts/toolkits/Gecko/__init__.py
@@ -1,4 +1,5 @@
from script import Script
from speech_generator import SpeechGenerator
from braille_generator import BrailleGenerator
+from script_utilities import Utilities
import script_settings
diff --git a/src/orca/scripts/toolkits/Gecko/bookmarks.py b/src/orca/scripts/toolkits/Gecko/bookmarks.py
index e4e370a..4a18f9f 100644
--- a/src/orca/scripts/toolkits/Gecko/bookmarks.py
+++ b/src/orca/scripts/toolkits/Gecko/bookmarks.py
@@ -100,14 +100,14 @@ class GeckoBookmarks(bookmarks.Bookmarks):
[cur_obj, cur_characterOffset] = self._script.getCaretContext()
# Are they the same object?
- if self._script.isSameObject(cur_obj, obj):
+ if self._script.utilities.isSameObject(cur_obj, obj):
# Translators: this announces that the current object is the same
# object pointed to by the bookmark.
#
speech.speak(_('bookmark is current object'))
return
# Are their parents the same?
- elif self._script.isSameObject(cur_obj.parent, obj.parent):
+ elif self._script.utilities.isSameObject(cur_obj.parent, obj.parent):
# Translators: this announces that the current object's parent and
# the parent of the object pointed to by the bookmark are the same.
#
@@ -271,7 +271,7 @@ class GeckoBookmarks(bookmarks.Bookmarks):
def pathToObj(self, path):
"""Return the object with the given path (relative to the
document frame). """
- returnobj = self._script.getDocumentFrame()
+ returnobj = self._script.utilities.documentFrame()
for childnumber in path:
try:
returnobj = returnobj[childnumber]
@@ -283,7 +283,7 @@ class GeckoBookmarks(bookmarks.Bookmarks):
def getURIKey(self):
"""Returns the URI key for a given page as a URI stripped of
parameters?query#fragment as seen in urlparse."""
- uri = self._script.getDocumentFrameURI()
+ uri = self._script.utilities.documentFrameURI()
if uri:
parsed_uri = urlparse.urlparse(uri)
return ''.join(parsed_uri[0:3])
diff --git a/src/orca/scripts/toolkits/Gecko/braille_generator.py b/src/orca/scripts/toolkits/Gecko/braille_generator.py
index 4ceba60..8932517 100644
--- a/src/orca/scripts/toolkits/Gecko/braille_generator.py
+++ b/src/orca/scripts/toolkits/Gecko/braille_generator.py
@@ -61,9 +61,8 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
imageLink = None
role = args.get('role', obj.getRole())
if role == pyatspi.ROLE_IMAGE:
- imageLink = self._script.getAncestor(obj,
- [pyatspi.ROLE_LINK],
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ imageLink = self._script.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_LINK], [pyatspi.ROLE_DOCUMENT_FRAME])
return imageLink
def _generateRoleName(self, obj, **args):
@@ -116,7 +115,7 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
result.extend(braille_generator.BrailleGenerator._generateName(
self, obj, **args))
if not result and role == pyatspi.ROLE_LIST_ITEM:
- result.append(self._script.expandEOCs(obj))
+ result.append(self._script.utilities.expandEOCs(obj))
link = None
if role == pyatspi.ROLE_LINK:
@@ -200,7 +199,7 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
if not len(result):
parent = obj.parent
if parent and parent.getRole() == pyatspi.ROLE_AUTOCOMPLETE:
- label = self._script.getDisplayedLabel(parent)
+ label = self._script.utilities.displayedLabel(parent)
if not label or not len(label):
label = parent.name
result.append(label)
@@ -210,7 +209,7 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
def _generateExpandedEOCs(self, obj, **args):
"""Returns the expanded embedded object characters for an object."""
result = []
- text = self._script.expandEOCs(obj)
+ text = self._script.utilities.expandEOCs(obj)
if text:
result.append(text)
return result
@@ -246,9 +245,8 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
# present combo boxes outside of Gecko.
#
if obj.getRole() == pyatspi.ROLE_MENU_ITEM:
- comboBox = self._script.getAncestor(obj,
- [pyatspi.ROLE_COMBO_BOX],
- [pyatspi.ROLE_FRAME])
+ comboBox = self._script.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_COMBO_BOX], [pyatspi.ROLE_FRAME])
if comboBox \
and not comboBox.getState().contains(pyatspi.STATE_EXPANDED):
obj = comboBox
diff --git a/src/orca/scripts/toolkits/Gecko/script.py b/src/orca/scripts/toolkits/Gecko/script.py
index 693b8aa..b30a4d8 100644
--- a/src/orca/scripts/toolkits/Gecko/script.py
+++ b/src/orca/scripts/toolkits/Gecko/script.py
@@ -71,6 +71,7 @@ from speech_generator import SpeechGenerator
from formatting import Formatting
from bookmarks import GeckoBookmarks
from structural_navigation import GeckoStructuralNavigation
+from script_utilities import Utilities
from orca.orca_i18n import _
from orca.speech_generator import Pause
@@ -196,7 +197,7 @@ class Script(default.Script):
# two seconds! This is a Firefox bug. We'll try to improve things
# by storing attributes.
#
- self._currentAttrs = {}
+ self.currentAttrs = {}
# Last focused frame. We are only interested in frame focused events
# when it is a different frame, so here we store the last frame
@@ -323,6 +324,11 @@ class Script(default.Script):
"""Returns the formatting strings for this script."""
return Formatting(self)
+ def getUtilities(self):
+ """Returns the utilites for this script."""
+
+ return Utilities(self)
+
def getEnabledStructuralNavigationTypes(self):
"""Returns a list of the structural navigation object types
enabled in this script.
@@ -1207,7 +1213,7 @@ class Script(default.Script):
# find one, or if the "say all by" mode is not sentence, we'll
# just start the sayAll from at the beginning of this line/object.
#
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text:
[line, startOffset, endOffset] = \
text.getTextAtOffset(characterOffset,
@@ -1242,7 +1248,7 @@ class Script(default.Script):
contents[min(i, len(contents)-1)]
[element, voice] = clumped[i]
if isinstance(element, basestring):
- element = self.adjustForRepeats(element)
+ element = self.utilities.adjustForRepeats(element)
if isinstance(element, Pause):
# At the moment, SayAllContext is expecting a string; not
# a Pause. For now, being conservative and catching that
@@ -1313,7 +1319,7 @@ class Script(default.Script):
# instead -- across FF 3.0, 3.1, and 3.2.
#
enoughSelected = False
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text and text.getNSelections():
[start, end] = text.getSelection(0)
offset = max(offset, start)
@@ -1339,8 +1345,8 @@ class Script(default.Script):
# events from the Find entry, so we have to compare
# offsets.
#
- if self.isSameObject(origObj, obj) and (origOffset > offset) \
- and lineChanged:
+ if self.utilities.isSameObject(origObj, obj) \
+ and (origOffset > offset) and lineChanged:
self.madeFindAnnouncement = False
if lineChanged or not self.madeFindAnnouncement or \
@@ -1364,85 +1370,6 @@ class Script(default.Script):
return True
- def getDisplayedText(self, obj):
- """Returns the text being displayed for an object.
-
- Arguments:
- - obj: the object
-
- Returns the text being displayed for an object or None if there isn't
- any text being shown. Overridden in this script because we have lots
- of whitespace we need to remove.
- """
-
- displayedText = default.Script.getDisplayedText(self, obj)
- if displayedText \
- and not (obj.getState().contains(pyatspi.STATE_EDITABLE) \
- or obj.getRole() in [pyatspi.ROLE_ENTRY,
- pyatspi.ROLE_PASSWORD_TEXT]):
- displayedText = displayedText.strip()
- # Some ARIA widgets (e.g. the list items in the chat box
- # in gmail) implement the accessible text interface but
- # only contain whitespace.
- #
- if not displayedText \
- and obj.getState().contains(pyatspi.STATE_FOCUSED):
- label = self.getDisplayedLabel(obj)
- if not label:
- displayedText = obj.name
-
- return displayedText
-
- def getDisplayedLabel(self, obj):
- """If there is an object labelling the given object, return the
- text being displayed for the object labelling this object.
- Otherwise, return None. Overridden here to handle instances
- of bogus labels and form fields where a lack of labels necessitates
- our attempt to guess the text that is functioning as a label.
-
- Argument:
- - obj: the object in question
-
- Returns the string of the object labelling this object, or None
- if there is nothing of interest here.
- """
-
- string = None
- labels = self.findDisplayedLabel(obj)
- for label in labels:
- # Check to see if the official labels are valid.
- #
- bogus = False
- if self.inDocumentContent() \
- and obj.getRole() in [pyatspi.ROLE_COMBO_BOX,
- pyatspi.ROLE_LIST]:
- # Bogus case #1:
- # <label></label> surrounding the entire combo box/list which
- # makes the entire combo box's/list's contents serve as the
- # label. We can identify this case because the child of the
- # label is the combo box/list. See bug #428114, #441476.
- #
- if label.childCount:
- bogus = (label[0].getRole() == obj.getRole())
-
- if not bogus:
- # Bogus case #2:
- # <label></label> surrounds not just the text serving as the
- # label, but whitespace characters as well (e.g. the text
- # serving as the label is on its own line within the HTML).
- # Because of the Mozilla whitespace bug, these characters
- # will become part of the label which will cause the label
- # and name to no longer match and Orca to seemingly repeat
- # the label. Therefore, strip out surrounding whitespace.
- # See bug #441610 and
- # https://bugzilla.mozilla.org/show_bug.cgi?id=348901
- #
- expandedLabel = self.expandEOCs(label)
- if expandedLabel:
- string = self.appendString(string, expandedLabel.strip())
-
- return string
-
def onCaretMoved(self, event):
"""Caret movement in Gecko is somewhat unreliable and
unpredictable, but we need to handle it. When we detect caret
@@ -1474,7 +1401,7 @@ class Script(default.Script):
#
[obj, characterOffset] = self.getCaretContext()
if max(0, characterOffset) == event.detail1 \
- and self.isSameObject(obj, event.source):
+ and self.utilities.isSameObject(obj, event.source):
return
if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent) \
@@ -1493,7 +1420,8 @@ class Script(default.Script):
# frame.
#
if eventSourceRole != pyatspi.ROLE_ENTRY \
- and self.isSameObject(event.source, orca_state.locusOfFocus):
+ and self.utilities.isSameObject(
+ event.source, orca_state.locusOfFocus):
return
# We are getting extraneous events that are not being caught
@@ -1514,7 +1442,7 @@ class Script(default.Script):
# components:
# scheme://netloc/path;parameters?query#fragment.
try:
- uri = self.getURI(orca_state.locusOfFocus)
+ uri = self.utilities.uri(orca_state.locusOfFocus)
uriInfo = urlparse.urlparse(uri)
except:
pass
@@ -1534,7 +1462,7 @@ class Script(default.Script):
return
elif self.isAriaWidget(orca_state.locusOfFocus) \
- and self.isSameObject(event.source,
+ and self.utilities.isSameObject(event.source,
orca_state.locusOfFocus.parent):
return
@@ -1547,7 +1475,7 @@ class Script(default.Script):
# If we're in the Find toolbar, we also want to present
# the results.
#
- if self.inFindToolbar():
+ if self.utilities.inFindToolbar():
self.presentFindResults(event.source, event.detail1)
else:
self.setCaretContext(event.source, event.detail1)
@@ -1595,7 +1523,7 @@ class Script(default.Script):
if self.inMouseOverObject:
obj = self.lastMouseOverObject
while obj and (obj != obj.parent):
- if self.isSameObject(event.source, obj):
+ if self.utilities.isSameObject(event.source, obj):
self.restorePreMouseOverContext()
break
obj = obj.parent
@@ -1764,9 +1692,9 @@ class Script(default.Script):
# unless it happens to be the same object as our current
# caret context.
#
- if self.isSameObject(event.source, self._objectForFocusGrab):
+ if self.utilities.isSameObject(event.source, self._objectForFocusGrab):
[obj, characterOffset] = self.getCaretContext()
- if not self.isSameObject(event.source, obj):
+ if not self.utilities.isSameObject(event.source, obj):
return
self._objectForFocusGrab = None
@@ -1778,7 +1706,7 @@ class Script(default.Script):
# that really holds the caret.
#
if eventSourceRole == pyatspi.ROLE_PANEL:
- documentFrame = self.getDocumentFrame()
+ documentFrame = self.utilities.documentFrame()
if documentFrame and (documentFrame.parent == event.source):
return
else:
@@ -1789,11 +1717,11 @@ class Script(default.Script):
# If we don't ignore this event, we'll loop to the top
# of the panel.
#
- containingPanel = \
- self.getAncestor(orca_state.locusOfFocus,
- [pyatspi.ROLE_PANEL],
- [pyatspi.ROLE_DOCUMENT_FRAME])
- if self.isSameObject(containingPanel, event.source):
+ containingPanel = self.utilities.ancestorWithRole(
+ orca_state.locusOfFocus,
+ [pyatspi.ROLE_PANEL],
+ [pyatspi.ROLE_DOCUMENT_FRAME])
+ if self.utilities.isSameObject(containingPanel, event.source):
return
# When we get a focus event on the document frame, it's usually
@@ -1820,8 +1748,9 @@ class Script(default.Script):
[obj, characterOffset] = \
self.findFirstCaretContext(event.source, 0)
self.setCaretContext(obj, characterOffset)
- if not self.isSameObject(event.source, obj):
- if not self.isSameObject(obj, orca_state.locusOfFocus):
+ if not self.utilities.isSameObject(event.source, obj):
+ if not self.utilities.isSameObject(
+ obj, orca_state.locusOfFocus):
orca.setLocusOfFocus(
event, obj, notifyPresentationManager=False)
# If an alert got focus, let's do the best we can to
@@ -1850,9 +1779,9 @@ class Script(default.Script):
- event: the Event
"""
- text = self.queryNonEmptyText(event.source)
+ text = self.utilities.queryNonEmptyText(event.source)
hypertext = event.source.queryHypertext()
- linkIndex = self.getLinkIndex(event.source, text.caretOffset)
+ linkIndex = self.utilities.linkIndex(event.source, text.caretOffset)
if linkIndex >= 0:
link = hypertext.getLink(linkIndex)
@@ -2096,7 +2025,7 @@ class Script(default.Script):
pyatspi.ROLE_STATUS_BAR, \
pyatspi.ROLE_FRAME, \
pyatspi.ROLE_APPLICATION]
- if not self.isDesiredFocusedItem(event.source, rolesList):
+ if not self.utilities.hasMatchingHierarchy(event.source, rolesList):
default.Script.handleProgressBarUpdate(self, event, obj)
def visualAppearanceChanged(self, event, obj):
@@ -2129,7 +2058,7 @@ class Script(default.Script):
"""
# Sometimes we get different accessibles for the same object.
#
- if self.isSameObject(oldLocusOfFocus, newLocusOfFocus):
+ if self.utilities.isSameObject(oldLocusOfFocus, newLocusOfFocus):
return
# We always automatically go back to focus tracking mode when
@@ -2142,7 +2071,7 @@ class Script(default.Script):
# at us.
#
if newLocusOfFocus and self.inDocumentContent(newLocusOfFocus):
- text = self.queryNonEmptyText(newLocusOfFocus)
+ text = self.utilities.queryNonEmptyText(newLocusOfFocus)
if text:
caretOffset = text.caretOffset
@@ -2155,9 +2084,11 @@ class Script(default.Script):
#
if oldLocusOfFocus and \
not self.inDocumentContent(oldLocusOfFocus):
- oldFrame = self.getFrame(oldLocusOfFocus)
- newFrame = self.getFrame(newLocusOfFocus)
- if self.isSameObject(oldFrame, newFrame) or \
+ oldFrame = self.utilities.ancestorWithRole(
+ oldLocusOfFocus, [pyatspi.ROLE_FRAME], [])
+ newFrame = self.utilities.ancestorWithRole(
+ newLocusOfFocus, [pyatspi.ROLE_FRAME], [])
+ if self.utilities.isSameObject(oldFrame, newFrame) or \
newLocusOfFocus.getRole() == pyatspi.ROLE_DIALOG:
self.setCaretPosition(newLocusOfFocus, caretOffset)
self.presentLine(newLocusOfFocus, caretOffset)
@@ -2182,7 +2113,7 @@ class Script(default.Script):
# If we've just landed in the Find toolbar, reset
# self.madeFindAnnouncement.
#
- if newLocusOfFocus and self.inFindToolbar(newLocusOfFocus):
+ if newLocusOfFocus and self.utilities.inFindToolbar(newLocusOfFocus):
self.madeFindAnnouncement = False
# We'll ignore focus changes when the document frame is busy.
@@ -2191,7 +2122,7 @@ class Script(default.Script):
# is really busy first and also that the event is not coming
# from an object within a dialog box or alert.
#
- documentFrame = self.getDocumentFrame()
+ documentFrame = self.utilities.documentFrame()
if documentFrame:
self._loadingDocumentContent = \
documentFrame.getState().contains(pyatspi.STATE_BUSY)
@@ -2199,9 +2130,8 @@ class Script(default.Script):
if self._loadingDocumentContent and event and event.source:
dialogRoles = [pyatspi.ROLE_DIALOG, pyatspi.ROLE_ALERT]
inDialog = event.source.getRole() in dialogRoles \
- or self.getAncestor(event.source,
- dialogRoles,
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ or self.utilities.ancestorWithRole(
+ event.source, dialogRoles, [pyatspi.ROLE_DOCUMENT_FRAME])
if not inDialog:
return
@@ -2255,12 +2185,12 @@ class Script(default.Script):
if candidate.getRole() in [pyatspi.ROLE_LIST,
pyatspi.ROLE_COMBO_BOX] \
and candidate.getState().contains(pyatspi.STATE_FOCUSABLE) \
- and not self.isSameObject(obj, candidate):
- start = self.getCharacterOffsetInParent(candidate)
+ and not self.utilities.isSameObject(obj, candidate):
+ start = self.utilities.characterOffsetInParent(candidate)
end = start + 1
candidate = candidate.parent
- if self.isSameObject(obj, candidate) \
+ if self.utilities.isSameObject(obj, candidate) \
and start <= offset < end:
index = contents.index(content)
break
@@ -2302,7 +2232,7 @@ class Script(default.Script):
self._previousLineContents = None
self.currentLineContents = None
self._nextLineContents = None
- self._currentAttrs = {}
+ self.currentAttrs = {}
def presentLine(self, obj, offset):
"""Presents the current line in speech and in braille.
@@ -2353,7 +2283,7 @@ class Script(default.Script):
#
needToRefresh = False
lineContentsOffset = focusedCharacterOffset
- focusedObjText = self.queryNonEmptyText(focusedObj)
+ focusedObjText = self.utilities.queryNonEmptyText(focusedObj)
if focusedObjText:
char = focusedObjText.getText(focusedCharacterOffset,
focusedCharacterOffset + 1)
@@ -2438,9 +2368,8 @@ class Script(default.Script):
and (isLastObject or not obj.childCount):
heading = obj
elif isLastObject:
- heading = self.getAncestor(obj,
- [pyatspi.ROLE_HEADING],
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ heading = self.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_HEADING], [pyatspi.ROLE_DOCUMENT_FRAME])
else:
heading = None
@@ -2498,17 +2427,17 @@ class Script(default.Script):
if role in layoutRoles:
acc1 = obj
else:
- acc1 = self.getAncestor(obj,
- layoutRoles,
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ acc1 = self.utilities.ancestorWithRole(
+ obj, layoutRoles, [pyatspi.ROLE_DOCUMENT_FRAME])
if acc1:
if lastObj.getRole() == acc1.getRole():
acc2 = lastObj
else:
- acc2 = self.getAncestor(lastObj,
- layoutRoles,
- [pyatspi.ROLE_DOCUMENT_FRAME])
- if not self.isSameObject(acc1, acc2):
+ acc2 = self.utilities.ancestorWithRole(
+ lastObj,
+ layoutRoles,
+ [pyatspi.ROLE_DOCUMENT_FRAME])
+ if not self.utilities.isSameObject(acc1, acc2):
self.addToLineAsBrailleRegion(" ", line)
self.addBrailleRegionsToLine(regions, line)
@@ -2543,7 +2472,7 @@ class Script(default.Script):
return
[obj, characterOffset] = self.getCaretContext()
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text:
# If the caret is at the end of text and we're not in an
# entry, something bad is going on, so decrement the offset
@@ -2570,7 +2499,7 @@ class Script(default.Script):
return
[obj, characterOffset] = self.getCaretContext()
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text:
# [[[TODO: WDW - the caret might be at the end of the text.
# Not quite sure what to do in this case. What we'll do here
@@ -2596,7 +2525,7 @@ class Script(default.Script):
if obj.getRole() != pyatspi.ROLE_ENTRY:
self.speakContents(wordContents)
else:
- word = textObj.queryText().getText(startOffset, endOffset)
+ word = self.utilities.substring(textObj, startOffset, endOffset)
speech.speak([word], self.getACSS(textObj, word))
def sayLine(self, obj):
@@ -2612,7 +2541,7 @@ class Script(default.Script):
return
[obj, characterOffset] = self.getCaretContext()
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text:
# [[[TODO: WDW - the caret might be at the end of the text.
# Not quite sure what to do in this case. What we'll do here
@@ -2685,14 +2614,14 @@ class Script(default.Script):
self.dumpInfo(obj.parent)
print "---"
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text and obj.getRole() != pyatspi.ROLE_DOCUMENT_FRAME:
string = text.getText(0, -1)
else:
string = ""
print obj, obj.name, obj.getRole(), \
obj.accessible.getIndexInParent(), string
- offset = self.getCharacterOffsetInParent(obj)
+ offset = self.utilities.characterOffsetInParent(obj)
if offset >= 0:
print " offset =", offset
@@ -2713,7 +2642,7 @@ class Script(default.Script):
[obj, characterOffset] = self.getCaretContext()
while obj:
if True or obj.getState().contains(pyatspi.STATE_SHOWING):
- if self.queryNonEmptyText(obj):
+ if self.utilities.queryNonEmptyText(obj):
# Check for text being on a different line. Gecko
# gives us odd character extents sometimes, so we
# defensively ignore those.
@@ -2780,7 +2709,7 @@ class Script(default.Script):
contents += "\n"
elif obj.getRole() == pyatspi.ROLE_TABLE_CELL:
parent = obj.parent
- index = self.getCellIndex(obj)
+ index = self.utilities.cellIndex(obj)
if parent.queryTable().getColumnAtIndex(index) != 0:
contents += " "
elif obj.getRole() == pyatspi.ROLE_LINK:
@@ -2811,7 +2740,7 @@ class Script(default.Script):
extents = self.getBoundary(
self.getExtents(obj, startOffset, endOffset),
extents)
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text:
string += "[%s] text='%s' " % (obj.getRole(),
text.getText(startOffset,
@@ -2830,44 +2759,11 @@ class Script(default.Script):
# #
####################################################################
- def queryNonEmptyText(self, obj):
- """Get the text interface associated with an object, if it is
- non-empty.
-
- Arguments:
- - obj: an accessible object
- """
-
- try:
- text = obj.queryText()
- except:
- pass
- else:
- if text.characterCount:
- return text
-
- return None
-
- def inFindToolbar(self, obj=None):
- """Returns True if the given object is in the Find toolbar.
-
- Arguments:
- - obj: an accessible object
- """
-
- if not obj:
- obj = orca_state.locusOfFocus
-
- if obj and obj.getRole() == pyatspi.ROLE_ENTRY \
- and obj.parent.getRole() == pyatspi.ROLE_TOOL_BAR:
- return True
-
- return False
-
def inDocumentContent(self, obj=None):
"""Returns True if the given object (defaults to the current
locus of focus is in the document content).
"""
+
if not obj:
obj = orca_state.locusOfFocus
try:
@@ -2887,85 +2783,6 @@ class Script(default.Script):
self.generatorCache['inDocumentContent'][obj] = result
return self.generatorCache['inDocumentContent'][obj]
- def getDocumentFrame(self):
- """Returns the document frame that holds the content being shown."""
-
- # [[[TODO: WDW - this is based upon the 12-Oct-2006 implementation
- # that uses the EMBEDS relation on the top level frame as a means
- # to find the document frame. Future implementations will break
- # this.]]]
- #
- documentFrame = None
- for child in self.app:
- if child.getRole() == pyatspi.ROLE_FRAME:
- relationSet = child.getRelationSet()
- for relation in relationSet:
- if relation.getRelationType() \
- == pyatspi.RELATION_EMBEDS:
- documentFrame = relation.getTarget(0)
- if documentFrame.getState().contains( \
- pyatspi.STATE_SHOWING):
- break
- else:
- documentFrame = None
-
- # Certain add-ons can interfere with the above approach. But we
- # should have a locusOfFocus. If so look up and try to find the
- # document frame. See bug 537303.
- #
- if not documentFrame:
- documentFrame = self.getAncestor(orca_state.locusOfFocus,
- [pyatspi.ROLE_DOCUMENT_FRAME],
- [pyatspi.ROLE_FRAME])
- return documentFrame
-
- def getURI(self, obj):
- """Return the URI for a given link object.
-
- Arguments:
- - obj: the Accessible object.
- """
- # Getting a link's URI requires a little workaround due to
- # https://bugzilla.mozilla.org/show_bug.cgi?id=379747. You should be
- # able to use getURI() directly on the link but instead must use
- # ihypertext.getLink(0) on parent then use getURI on returned
- # ihyperlink.
- try:
- ihyperlink = obj.parent.queryHypertext().getLink(0)
- except:
- return None
- else:
- try:
- return ihyperlink.getURI(0)
- except:
- return None
-
- def getDocumentFrameURI(self):
- """Returns the URI of the document frame that is active."""
- documentFrame = self.getDocumentFrame()
- if documentFrame:
- # If the document frame belongs to a Thunderbird message which
- # has just been deleted, getAttributes() will crash Thunderbird.
- #
- if not documentFrame.getState().contains(pyatspi.STATE_DEFUNCT):
- attrs = documentFrame.queryDocument().getAttributes()
- for attr in attrs:
- if attr.startswith('DocURL'):
- return attr[7:]
- return None
-
- def getUnicodeText(self, obj):
- """Returns the unicode text for an object or None if the object
- doesn't implement the accessible text specialization.
- """
-
- text = self.queryNonEmptyText(obj)
- if text:
- unicodeText = text.getText(0, -1).decode("UTF-8")
- else:
- unicodeText = None
- return unicodeText
-
def useCaretNavigationModel(self, keyboardEvent):
"""Returns True if we should do our own caret navigation.
"""
@@ -3327,7 +3144,7 @@ class Script(default.Script):
# the text. Similarly, if it's a menu in a combo box, get the
# extents of the combo box.
#
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text and obj.getRole() != pyatspi.ROLE_MENU_ITEM:
extents = text.getRangeExtents(startOffset, endOffset, 0)
elif obj.getRole() == pyatspi.ROLE_MENU \
@@ -3444,51 +3261,22 @@ class Script(default.Script):
return None
- def getCellIndex(self, obj):
- """Returns the index of the cell which should be used with the
- table interface. This is necessary because we cannot count on
- the index we need being the same as the index in the parent.
- See, for example, tables with captions and tables with rows
- that have attributes."""
-
- index = -1
- parent = self.getAncestor(obj,
- [pyatspi.ROLE_TABLE,
- pyatspi.ROLE_TREE_TABLE,
- pyatspi.ROLE_TREE],
- [pyatspi.ROLE_DOCUMENT_FRAME])
- try:
- table = parent.queryTable()
- except:
- pass
- else:
- attrs = dict([attr.split(':', 1) for attr in obj.getAttributes()])
- index = attrs.get('table-cell-index')
- if index:
- index = int(index)
- else:
- index = obj.getIndexInParent()
-
- return index
-
def getCellCoordinates(self, obj):
"""Returns the [row, col] of a ROLE_TABLE_CELL or [0, 0]
if the coordinates cannot be found.
"""
if obj.getRole() != pyatspi.ROLE_TABLE_CELL:
- obj = self.getAncestor(obj,
- [pyatspi.ROLE_TABLE_CELL],
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ obj = self.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_TABLE_CELL], [pyatspi.ROLE_DOCUMENT_FRAME])
- parentTable = self.getAncestor(obj,
- [pyatspi.ROLE_TABLE],
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ parentTable = self.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_TABLE], [pyatspi.ROLE_DOCUMENT_FRAME])
try:
table = parentTable.queryTable()
except:
pass
else:
- index = self.getCellIndex(obj)
+ index = self.utilities.cellIndex(obj)
row = table.getRowAtIndex(index)
col = table.getColumnAtIndex(index)
return [row, col]
@@ -3503,7 +3291,7 @@ class Script(default.Script):
- obj: the table cell to examime
"""
- text = self.getDisplayedText(obj)
+ text = self.utilities.displayedText(obj)
if text and text != u'\u00A0':
return False
else:
@@ -3600,7 +3388,7 @@ class Script(default.Script):
useless = False
- textObj = self.queryNonEmptyText(obj)
+ textObj = self.utilities.queryNonEmptyText(obj)
if not textObj and obj.getRole() == pyatspi.ROLE_PARAGRAPH:
# Under these circumstances, this object is useless even
# if it is the child of a link.
@@ -3609,16 +3397,15 @@ class Script(default.Script):
elif obj.getRole() in [pyatspi.ROLE_IMAGE, \
pyatspi.ROLE_TABLE_CELL, \
pyatspi.ROLE_SECTION]:
- text = self.getDisplayedText(obj)
+ text = self.utilities.displayedText(obj)
if (not text) or (len(text) == 0):
- text = self.getDisplayedLabel(obj)
+ text = self.utilities.displayedLabel(obj)
if (not text) or (len(text) == 0):
useless = True
if useless:
- link = self.getAncestor(obj,
- [pyatspi.ROLE_LINK],
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ link = self.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_LINK], [pyatspi.ROLE_DOCUMENT_FRAME])
if link:
if obj.getRole() == pyatspi.ROLE_IMAGE:
# If this object had alternative text and/or a title,
@@ -3628,7 +3415,7 @@ class Script(default.Script):
# bug 584540.
#
for child in obj.parent:
- if self.getDisplayedText(child):
+ if self.utilities.displayedText(child):
# Some other sibling is presenting information.
# We'll treat this image as useless.
#
@@ -3650,7 +3437,8 @@ class Script(default.Script):
# heading or something else that might result in
# it being on its own line.
#
- textObj = self.queryNonEmptyText(obj.parent)
+ textObj = \
+ self.utilities.queryNonEmptyText(obj.parent)
if textObj:
text = textObj.getText(0, -1).decode("UTF-8")
text = text.replace(\
@@ -3667,17 +3455,6 @@ class Script(default.Script):
return useless
- def isLayoutOnly(self, obj):
- """Returns True if the given object is for layout purposes only."""
-
- if self.isUselessObject(obj):
- debug.println(debug.LEVEL_FINEST,
- "Object deemed to be useless: %s" % obj)
- return True
-
- else:
- return default.Script.isLayoutOnly(self, obj)
-
def pursueForFlatReview(self, obj):
"""Determines if we should look any further at the object
for flat review."""
@@ -3698,107 +3475,6 @@ class Script(default.Script):
return state.contains(pyatspi.STATE_SHOWING) \
and state.contains(pyatspi.STATE_VISIBLE)
- def getShowingDescendants(self, parent):
- """Given an accessible object, returns a list of accessible children
- that are actually showing/visible/pursable for flat review. We're
- overriding the default method here primarily to handle enormous
- tree tables (such as the Thunderbird message list) which do not
- manage their descendants.
-
- Arguments:
- - parent: The accessible which manages its descendants
-
- Returns a list of Accessible descendants which are showing.
- """
-
- if not parent:
- return []
-
- # If this object is not a tree table, if it manages its descendants,
- # or if it doesn't have very many children, let the default script
- # handle it.
- #
- if parent.getRole() != pyatspi.ROLE_TREE_TABLE \
- or parent.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS) \
- or parent.childCount <= 50:
- return default.Script.getShowingDescendants(self, parent)
-
- try:
- table = parent.queryTable()
- except NotImplementedError:
- return []
-
- descendants = []
-
- # First figure out what columns are visible as there's no point
- # in examining cells which we know won't be visible.
- #
- visibleColumns = []
- for i in range(table.nColumns):
- header = table.getColumnHeader(i)
- if self.pursueForFlatReview(header):
- visibleColumns.append(i)
- descendants.append(header)
-
- if not len(visibleColumns):
- return []
-
- # Now that we know in which columns we can expect to find visible
- # cells, try to quickly locate a visible row.
- #
- startingRow = 0
-
- # If we have one or more selected items, odds are fairly good
- # (although not guaranteed) that one of those items happens to
- # be showing. Failing that, calculate how many rows can fit in
- # the exposed portion of the tree table and scroll down.
- #
- selectedRows = table.getSelectedRows()
- for row in selectedRows:
- acc = table.getAccessibleAt(row, visibleColumns[0])
- if self.pursueForFlatReview(acc):
- startingRow = row
- break
- else:
- try:
- tableExtents = parent.queryComponent().getExtents(0)
- acc = table.getAccessibleAt(0, visibleColumns[0])
- cellExtents = acc.queryComponent().getExtents(0)
- except:
- pass
- else:
- rowIncrement = max(1, tableExtents.height / cellExtents.height)
- for row in range(0, table.nRows, rowIncrement):
- acc = table.getAccessibleAt(row, visibleColumns[0])
- if acc and self.pursueForFlatReview(acc):
- startingRow = row
- break
-
- # Get everything after this point which is visible.
- #
- for row in range(startingRow, table.nRows):
- acc = table.getAccessibleAt(row, visibleColumns[0])
- if self.pursueForFlatReview(acc):
- descendants.append(acc)
- for col in visibleColumns[1:len(visibleColumns)]:
- descendants.append(table.getAccessibleAt(row, col))
- else:
- break
-
- # Get everything before this point which is visible.
- #
- for row in range(startingRow - 1, -1, -1):
- acc = table.getAccessibleAt(row, visibleColumns[0])
- if self.pursueForFlatReview(acc):
- thisRow = [acc]
- for col in visibleColumns[1:len(visibleColumns)]:
- thisRow.append(table.getAccessibleAt(row, col))
- descendants[0:0] = thisRow
- else:
- break
-
- return descendants
-
def getHeadingLevel(self, obj):
"""Determines the heading level of the given object. A value
of 0 means there is no heading level."""
@@ -3819,43 +3495,11 @@ class Script(default.Script):
return level
- def getNodeLevel(self, obj):
- """ Determines the level of at which this object is at by using the
- object attribute 'level'. To be consistent with default.getNodeLevel()
- this value is 0-based (Gecko return is 1-based) """
-
- if obj is None or obj.getRole() == pyatspi.ROLE_HEADING \
- or (obj.parent and obj.parent.getRole() == pyatspi.ROLE_MENU):
- return -1
-
- try:
- state = obj.getState()
- except:
- return -1
- else:
- if state.contains(pyatspi.STATE_DEFUNCT):
- # Yelp (or perhaps the work-in-progress a11y patch)
- # seems to be guilty of this.
- #
- #print "getNodeLevel - obj is defunct", obj
- debug.println(debug.LEVEL_WARNING,
- "getNodeLevel - obj is defunct")
- debug.printStack(debug.LEVEL_WARNING)
- return -1
-
- attrs = obj.getAttributes()
- if attrs is None:
- return -1
- for attr in attrs:
- if attr.startswith("level:"):
- return int(attr[6:]) - 1
- return -1
-
def getTopOfFile(self):
"""Returns the object and first caret offset at the top of the
document frame."""
- documentFrame = self.getDocumentFrame()
+ documentFrame = self.utilities.documentFrame()
[obj, offset] = self.findFirstCaretContext(documentFrame, 0)
return [obj, offset]
@@ -3864,8 +3508,8 @@ class Script(default.Script):
"""Returns the object and last caret offset at the bottom of the
document frame."""
- documentFrame = self.getDocumentFrame()
- text = self.queryNonEmptyText(documentFrame)
+ documentFrame = self.utilities.documentFrame()
+ text = self.utilities.queryNonEmptyText(documentFrame)
if text:
char = text.getText(text.characterCount - 1, text.characterCount)
if char != self.EMBEDDED_OBJECT_CHARACTER:
@@ -3878,7 +3522,7 @@ class Script(default.Script):
# for text that follows.
#
if obj.getRole() == pyatspi.ROLE_LINK:
- text = self.queryNonEmptyText(obj.parent)
+ text = self.utilities.queryNonEmptyText(obj.parent)
if text:
char = text.getText(text.characterCount - 1,
text.characterCount)
@@ -3889,7 +3533,7 @@ class Script(default.Script):
# and not have children of its own. Therefore, it should have text.
# If it doesn't, we don't want to be here.
#
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text:
offset = text.characterCount - 1
else:
@@ -3898,7 +3542,8 @@ class Script(default.Script):
while obj:
[lastObj, lastOffset] = self.findNextCaretInOrder(obj, offset)
if not lastObj \
- or self.isSameObject(lastObj, obj) and (lastOffset == offset):
+ or self.utilities.isSameObject(lastObj, obj) \
+ and (lastOffset == offset):
break
[obj, offset] = [lastObj, lastOffset]
@@ -3939,9 +3584,8 @@ class Script(default.Script):
text = ""
extents = (0, 0, 0, 0)
isField = False
- parentTable = self.getAncestor(cell,
- [pyatspi.ROLE_TABLE],
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ parentTable = self.utilities.ancestorWithRole(
+ cell, [pyatspi.ROLE_TABLE], [pyatspi.ROLE_DOCUMENT_FRAME])
if not cell or cell.getRole() != pyatspi.ROLE_TABLE_CELL \
or not parentTable:
return [newCell, text, extents, isField]
@@ -4025,7 +3669,7 @@ class Script(default.Script):
return []
objects = []
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text:
if boundary:
[string, start, end] = \
@@ -4087,7 +3731,7 @@ class Script(default.Script):
for child in obj:
toAdd.extend(self.getObjectsFromEOCs(child, 0, boundary))
if len(toAdd):
- if self.isSameObject(objects[-1][0], obj):
+ if self.utilities.isSameObject(objects[-1][0], obj):
objects.pop()
objects.extend(toAdd)
@@ -4122,7 +3766,7 @@ class Script(default.Script):
index = len(lineContents) - 1
while labelGuess and index >= 0:
prevItem = lineContents[index]
- prevText = self.queryNonEmptyText(prevItem[0])
+ prevText = self.utilities.queryNonEmptyText(prevItem[0])
if prevText:
string = prevText.getText(prevItem[1], prevItem[2])
if labelGuess.endswith(string):
@@ -4134,7 +3778,7 @@ class Script(default.Script):
index -= 1
else:
- text = self.queryNonEmptyText(item[0])
+ text = self.utilities.queryNonEmptyText(item[0])
if text:
string = text.getText(item[1], item[2]).decode("UTF-8")
if not len(string.strip()):
@@ -4165,7 +3809,7 @@ class Script(default.Script):
forms, tables, visited and unvisited links.
"""
- docframe = self.getDocumentFrame()
+ docframe = self.utilities.documentFrame()
col = docframe.queryCollection()
# We will initialize these after the queryCollection() call in case
# Collection is not supported
@@ -4195,7 +3839,7 @@ class Script(default.Script):
elif role == pyatspi.ROLE_FORM:
forms += 1
elif role == pyatspi.ROLE_TABLE \
- and not self.isLayoutOnly(obj):
+ and not self.utilities.isLayoutOnly(obj):
tables += 1
elif role == pyatspi.ROLE_LINK:
if obj.getState().contains(pyatspi.STATE_VISITED):
@@ -4222,7 +3866,7 @@ class Script(default.Script):
# Start at the first object after document frame.
#
- obj = self.getDocumentFrame()[0]
+ obj = self.utilities.documentFrame()[0]
while obj:
nodetotal += 1
if obj == currentobj:
@@ -4233,7 +3877,7 @@ class Script(default.Script):
elif role == pyatspi.ROLE_FORM:
forms += 1
elif role == pyatspi.ROLE_TABLE \
- and not self.isLayoutOnly(obj):
+ and not self.utilities.isLayoutOnly(obj):
tables += 1
elif role == pyatspi.ROLE_LINK:
if obj.getState().contains(pyatspi.STATE_VISITED):
@@ -4532,9 +4176,8 @@ class Script(default.Script):
# not in a table at all or are in a more complex layout table
# than this approach can handle.
#
- containingCell = self.getAncestor(obj,
- [pyatspi.ROLE_TABLE_CELL],
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ containingCell = self.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_TABLE_CELL], [pyatspi.ROLE_DOCUMENT_FRAME])
if not containingCell or containingCell.childCount > 1:
return guess
@@ -4651,11 +4294,11 @@ class Script(default.Script):
# Maybe we've already made a guess and saved it.
#
for field, label in self._guessedLabels.items():
- if self.isSameObject(field, obj):
+ if self.utilities.isSameObject(field, obj):
return label
parent = obj.parent
- text = self.queryNonEmptyText(parent)
+ text = self.utilities.queryNonEmptyText(parent)
# Because the guesswork is based upon spatial relations, if we're
# in a list, look from the perspective of the first list item rather
@@ -4720,9 +4363,9 @@ class Script(default.Script):
Returns [obj, characterOffset] that points to real content.
"""
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text:
- unicodeText = self.getUnicodeText(obj)
+ unicodeText = self.utilities.unicodeText(obj)
if characterOffset >= len(unicodeText):
if obj.getRole() != pyatspi.ROLE_ENTRY:
return [obj, -1]
@@ -4789,7 +4432,7 @@ class Script(default.Script):
"""
if not obj:
- obj = self.getDocumentFrame()
+ obj = self.utilities.documentFrame()
if not obj or not self.inDocumentContent(obj):
return [None, -1]
@@ -4805,9 +4448,9 @@ class Script(default.Script):
and obj.getRole() in [pyatspi.ROLE_COMBO_BOX,
pyatspi.ROLE_LIST]
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text:
- unicodeText = self.getUnicodeText(obj)
+ unicodeText = self.utilities.unicodeText(obj)
# Delete the final space character if we find it. Otherwise,
# we'll arrow to it. (We can't just strip the string otherwise
@@ -4844,7 +4487,7 @@ class Script(default.Script):
debug.printException(debug.LEVEL_SEVERE)
elif includeNonText and (startOffset < 0) \
- and (not self.isLayoutOnly(obj)):
+ and (not self.utilities.isLayoutOnly(obj)):
extents = obj.queryComponent().getExtents(0)
if (extents.width != 0) and (extents.height != 0):
return [obj, 0]
@@ -4852,12 +4495,13 @@ class Script(default.Script):
# If we're here, we need to start looking up the tree,
# going no higher than the document frame, of course.
#
- documentFrame = self.getDocumentFrame()
- if self.isSameObject(obj, documentFrame):
+ documentFrame = self.utilities.documentFrame()
+ if self.utilities.isSameObject(obj, documentFrame):
return [None, -1]
while obj.parent and obj != obj.parent:
- characterOffsetInParent = self.getCharacterOffsetInParent(obj)
+ characterOffsetInParent = \
+ self.utilities.characterOffsetInParent(obj)
if characterOffsetInParent >= 0:
return self.findNextCaretInOrder(obj.parent,
characterOffsetInParent,
@@ -4895,7 +4539,7 @@ class Script(default.Script):
"""
if not obj:
- obj = self.getDocumentFrame()
+ obj = self.utilities.documentFrame()
if not obj or not self.inDocumentContent(obj):
return [None, -1]
@@ -4911,9 +4555,9 @@ class Script(default.Script):
and obj.getRole() in [pyatspi.ROLE_COMBO_BOX,
pyatspi.ROLE_LIST]
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text:
- unicodeText = self.getUnicodeText(obj)
+ unicodeText = self.utilities.unicodeText(obj)
# Delete the final space character if we find it. Otherwise,
# we'll arrow to it. (We can't just strip the string otherwise
@@ -4954,7 +4598,7 @@ class Script(default.Script):
debug.printException(debug.LEVEL_SEVERE)
elif includeNonText and (startOffset < 0) \
- and (not self.isLayoutOnly(obj)):
+ and (not self.utilities.isLayoutOnly(obj)):
extents = obj.queryComponent().getExtents(0)
if (extents.width != 0) and (extents.height != 0):
return [obj, 0]
@@ -4962,12 +4606,13 @@ class Script(default.Script):
# If we're here, we need to start looking up the tree,
# going no higher than the document frame, of course.
#
- documentFrame = self.getDocumentFrame()
- if self.isSameObject(obj, documentFrame):
+ documentFrame = self.utilities.documentFrame()
+ if self.utilities.isSameObject(obj, documentFrame):
return [None, -1]
while obj.parent and obj != obj.parent:
- characterOffsetInParent = self.getCharacterOffsetInParent(obj)
+ characterOffsetInParent = \
+ self.utilities.characterOffsetInParent(obj)
if characterOffsetInParent >= 0:
return self.findPreviousCaretInOrder(obj.parent,
characterOffsetInParent,
@@ -5001,7 +4646,7 @@ class Script(default.Script):
# If the object is the document frame, the previous object is
# the one that follows us relative to our offset.
#
- if self.isSameObject(obj, documentFrame):
+ if self.utilities.isSameObject(obj, documentFrame):
[obj, characterOffset] = self.getCaretContext()
if not obj:
@@ -5009,7 +4654,7 @@ class Script(default.Script):
index = obj.getIndexInParent() - 1
if (index < 0):
- if not self.isSameObject(obj, documentFrame):
+ if not self.utilities.isSameObject(obj, documentFrame):
previousObj = obj.parent
else:
# We're likely at the very end of the document
@@ -5039,7 +4684,7 @@ class Script(default.Script):
# more complex than it really has to be.]]]
#
if not previousObj:
- if not self.isSameObject(obj, documentFrame):
+ if not self.utilities.isSameObject(obj, documentFrame):
previousObj = obj.parent
else:
previousObj = obj
@@ -5068,9 +4713,10 @@ class Script(default.Script):
index = previousObj.childCount - 1
while index >= 0:
child = previousObj[index]
- childOffset = self.getCharacterOffsetInParent(child)
+ childOffset = self.utilities.characterOffsetInParent(child)
if isinstance(child, pyatspi.Accessibility.Accessible) \
- and not (self.isSameObject(previousObj, documentFrame) \
+ and not (self.utilities.isSameObject(
+ previousObj, documentFrame) \
and childOffset > characterOffset):
previousObj = child
break
@@ -5079,7 +4725,7 @@ class Script(default.Script):
if index < 0:
break
- if self.isSameObject(previousObj, documentFrame):
+ if self.utilities.isSameObject(previousObj, documentFrame):
previousObj = None
return previousObj
@@ -5099,7 +4745,7 @@ class Script(default.Script):
# If the object is the document frame, the next object is
# the one that follows us relative to our offset.
#
- if self.isSameObject(obj, documentFrame):
+ if self.utilities.isSameObject(obj, documentFrame):
[obj, characterOffset] = self.getCaretContext()
if not obj:
@@ -5130,9 +4776,9 @@ class Script(default.Script):
if child is None:
index += 1
continue
- childOffset = self.getCharacterOffsetInParent(child)
+ childOffset = self.utilities.characterOffsetInParent(child)
if isinstance(child, pyatspi.Accessibility.Accessible) \
- and not (self.isSameObject(obj, documentFrame) \
+ and not (self.utilities.isSameObject(obj, documentFrame) \
and childOffset < characterOffset):
nextObj = child
break
@@ -5161,9 +4807,10 @@ class Script(default.Script):
# Go up until we find a parent that might have a sibling to
# the right for us.
#
- while (candidate.getIndexInParent() >= \
- (candidate.parent.childCount - 1)) \
- and not self.isSameObject(candidate, documentFrame):
+ while candidate and candidate.parent \
+ and candidate.getIndexInParent() >= \
+ candidate.parent.childCount - 1 \
+ and not self.utilities.isSameObject(candidate, documentFrame):
candidate = candidate.parent
# Now...let's get the sibling.
@@ -5171,7 +4818,7 @@ class Script(default.Script):
# [[[TODO: HACK - WDW Gecko's broken hierarchies make this
# a bit of a challenge.]]]
#
- if not self.isSameObject(candidate, documentFrame):
+ if not self.utilities.isSameObject(candidate, documentFrame):
index = candidate.getIndexInParent() + 1
while index < candidate.parent.childCount:
child = candidate.parent[index]
@@ -5197,22 +4844,11 @@ class Script(default.Script):
# #
####################################################################
- def isReadOnlyTextArea(self, obj):
- """Returns True if obj is a text entry area that is read only."""
- state = obj.getState()
- readOnly = obj.getRole() == pyatspi.ROLE_ENTRY \
- and state.contains(pyatspi.STATE_FOCUSABLE) \
- and not state.contains(pyatspi.STATE_EDITABLE)
- debug.println(debug.LEVEL_ALL,
- "Gecko.script.py:isReadOnlyTextArea=%s for %s" \
- % (readOnly, debug.getAccessibleDetails(obj)))
- return readOnly
-
def clearCaretContext(self):
"""Deletes all knowledge of a character context for the current
document frame."""
- documentFrame = self.getDocumentFrame()
+ documentFrame = self.utilities.documentFrame()
self._destroyLineCache()
try:
del self._documentFrameCaretContext[hash(documentFrame)]
@@ -5226,7 +4862,7 @@ class Script(default.Script):
# [[[TODO: WDW - probably should figure out how to destroy
# these contexts when a tab is killed.]]]
#
- documentFrame = self.getDocumentFrame()
+ documentFrame = self.utilities.documentFrame()
if not documentFrame:
return
@@ -5272,7 +4908,7 @@ class Script(default.Script):
# Determine the caretOffset.
#
- if self.isSameObject(obj, contextObj):
+ if self.utilities.isSameObject(obj, contextObj):
caretOffset = contextOffset
else:
try:
@@ -5290,7 +4926,7 @@ class Script(default.Script):
#
for content in contents:
candidate, startOffset, endOffset, string = content
- if self.isSameObject(candidate, obj) \
+ if self.utilities.isSameObject(candidate, obj) \
and (offset is None or (startOffset <= offset <= endOffset)):
return string.encode("UTF-8"), caretOffset, startOffset
@@ -5301,52 +4937,6 @@ class Script(default.Script):
#print "getTextLineAtCaret failed"
return default.Script.getTextLineAtCaret(self, obj, offset)
- def isWordMisspelled(self, obj, offset):
- """Identifies if the current word is flagged as misspelled by the
- application.
-
- Arguments:
- - obj: An accessible which implements the accessible text interface.
- - offset: Offset in the accessible's text for which to retrieve the
- attributes.
-
- Returns True if the word is flagged as misspelled.
- """
-
- attributes, start, end = self.getTextAttributes(obj, offset, True)
- error = attributes.get("invalid")
-
- return error == "spelling"
-
- def getTextAttributes(self, acc, offset, get_defaults=False):
- """Get the text attributes run for a given offset in a given accessible
-
- Arguments:
- - acc: An accessible.
- - offset: Offset in the accessible's text for which to retrieve the
- attributes.
- - get_defaults: Get the default attributes as well as the unique ones.
- Default is True
-
- Returns a dictionary of attributes, a start offset where the attributes
- begin, and an end offset. Returns ({}, 0, 0) if the accessible does not
- supprt the text attribute.
- """
-
- # For really large objects, a call to getAttributes can take up to
- # two seconds! This is a Firefox bug. We'll try to improve things
- # by storing attributes.
- #
- attrsForObj = self._currentAttrs.get(hash(acc)) or {}
- if attrsForObj.has_key(offset):
- return attrsForObj.get(offset)
-
- attrs = \
- default.Script.getTextAttributes(self, acc, offset, get_defaults)
- self._currentAttrs[hash(acc)] = {offset:attrs}
-
- return attrs
-
def searchForCaretLocation(self, acc):
"""Attempts to locate the caret on the page independent of our
caret context. This functionality is needed when a page loads
@@ -5386,7 +4976,7 @@ class Script(default.Script):
# [[[TODO: WDW - probably should figure out how to destroy
# these contexts when a tab is killed.]]]
#
- documentFrame = self.getDocumentFrame()
+ documentFrame = self.utilities.documentFrame()
if not documentFrame:
return [None, -1]
@@ -5429,7 +5019,7 @@ class Script(default.Script):
"""
try:
- unicodeText = self.getUnicodeText(obj)
+ unicodeText = self.utilities.unicodeText(obj)
return unicodeText[characterOffset].encode("UTF-8")
except:
return None
@@ -5453,7 +5043,7 @@ class Script(default.Script):
return []
boundary = boundary or pyatspi.TEXT_BOUNDARY_WORD_START
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text:
word = text.getTextAtOffset(characterOffset, boundary)
if word[1] < characterOffset <= word[2]:
@@ -5516,7 +5106,7 @@ class Script(default.Script):
# Find the beginning of this line w.r.t. this object.
#
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if not text:
offset = 0
else:
@@ -5536,8 +5126,10 @@ class Script(default.Script):
pObj, pOffset = self.findPreviousCaretInOrder(obj, offset)
if pObj:
obj, offset = pObj, pOffset
- text = self.queryNonEmptyText(obj)
- [line, start, end] = text.getTextAtOffset(offset, boundary)
+ text = self.utilities.queryNonEmptyText(obj)
+ if text:
+ [line, start, end] = \
+ text.getTextAtOffset(offset, boundary)
if start <= offset < end:
# So far so good. If the line doesn't begin with an EOC, we
@@ -5553,7 +5145,7 @@ class Script(default.Script):
childIndex = self.getChildIndex(obj, start)
if childIndex >= 0:
child = obj[childIndex]
- childText = self.queryNonEmptyText(child)
+ childText = self.utilities.queryNonEmptyText(child)
if not childText:
# It's probably an anchor. It might be something
# else, but that's okay because we do another
@@ -5614,10 +5206,10 @@ class Script(default.Script):
while not done:
[firstObj, start, end, string] = objects[0]
[prevObj, pOffset] = self.findPreviousCaretInOrder(firstObj, start)
- if not prevObj or self.isSameObject(prevObj, firstObj):
+ if not prevObj or self.utilities.isSameObject(prevObj, firstObj):
break
- text = self.queryNonEmptyText(prevObj)
+ text = self.utilities.queryNonEmptyText(prevObj)
if text:
line = text.getTextAtOffset(pOffset, boundary)
pOffset = line[1]
@@ -5660,14 +5252,14 @@ class Script(default.Script):
# the end offset by 1. If we find the same object, try again.
#
[nextObj, nOffset] = self.findNextCaretInOrder(lastObj, end - 1)
- if self.isSameObject(lastObj, nextObj):
+ if self.utilities.isSameObject(lastObj, nextObj):
[nextObj, nOffset] = \
self.findNextCaretInOrder(nextObj, nOffset)
- if not nextObj or self.isSameObject(nextObj, lastObj):
+ if not nextObj or self.utilities.isSameObject(nextObj, lastObj):
break
- text = self.queryNonEmptyText(nextObj)
+ text = self.utilities.queryNonEmptyText(nextObj)
if text:
line = text.getTextAfterOffset(nOffset, boundary)
nOffset = line[1]
@@ -5810,7 +5402,7 @@ class Script(default.Script):
#
if (role == pyatspi.ROLE_RADIO_BUTTON) \
and not self.isAriaWidget(obj):
- label = self.getDisplayedLabel(obj)
+ label = self.utilities.displayedLabel(obj)
if label:
utterances.append([label, self.getACSS(obj, label)])
@@ -5836,9 +5428,10 @@ class Script(default.Script):
if isHeading:
heading = obj
else:
- heading = self.getAncestor(obj,
- [pyatspi.ROLE_HEADING],
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ heading = self.utilities.ancestorWithRole(
+ obj,
+ [pyatspi.ROLE_HEADING],
+ [pyatspi.ROLE_DOCUMENT_FRAME])
if heading:
utterance.extend(\
@@ -5890,7 +5483,7 @@ class Script(default.Script):
clumped = self.clumpUtterances(utterances)
for [element, acss] in clumped:
if isinstance(element, basestring):
- element = self.adjustForRepeats(element)
+ element = self.utilities.adjustForRepeats(element)
speech.speak(element, acss, False)
def speakCharacterAtOffset(self, obj, characterOffset):
@@ -5919,10 +5512,6 @@ class Script(default.Script):
# #
####################################################################
- def setCaretOffset(self, obj, characterOffset):
- self.setCaretPosition(obj, characterOffset)
- self.updateBraille(obj)
-
def setCaretPosition(self, obj, characterOffset):
"""Sets the caret position to the given character offset in the
given object.
@@ -5941,7 +5530,7 @@ class Script(default.Script):
# open in several different tabs, and we keep track of
# where the caret is for each documentFrame.
#
- documentFrame = self.getDocumentFrame()
+ documentFrame = self.utilities.documentFrame()
if documentFrame:
self._documentFrameCaretContext[hash(documentFrame)] = caretContext
@@ -5958,7 +5547,7 @@ class Script(default.Script):
if obj \
and obj.getRole() in [pyatspi.ROLE_LIST, pyatspi.ROLE_COMBO_BOX] \
and obj.getState().contains(pyatspi.STATE_FOCUSABLE):
- characterOffset = self.getCharacterOffsetInParent(obj)
+ characterOffset = self.utilities.characterOffsetInParent(obj)
obj = obj.parent
self.setCaretContext(obj, characterOffset)
@@ -5987,7 +5576,8 @@ class Script(default.Script):
# back to the link and never being able to arrow through
# the text.
#
- if role == pyatspi.ROLE_LINK and self.queryNonEmptyText(obj):
+ if role == pyatspi.ROLE_LINK \
+ and self.utilities.queryNonEmptyText(obj):
self._objectForFocusGrab = None
break
@@ -6025,7 +5615,7 @@ class Script(default.Script):
# objectForFocus = objectForFocus.parent
self._objectForFocusGrab.queryComponent().grabFocus()
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text:
text.setCaretOffset(characterOffset)
if characterOffset == text.characterCount:
@@ -6228,7 +5818,7 @@ class Script(default.Script):
currentLine = self.currentLineContents
index = self.findObjectOnLine(obj, characterOffset, currentLine)
if index < 0:
- text = self.queryNonEmptyText(obj)
+ text = self.utilities.queryNonEmptyText(obj)
if text and text.characterCount == characterOffset:
characterOffset -= 1
currentLine = self.getLineContentsAtOffset(obj, characterOffset)
@@ -6274,7 +5864,7 @@ class Script(default.Script):
failureCount += 1
if currentLine == prevLine:
# print "find prev line still stuck", prevObj, prevOffset
- documentFrame = self.getDocumentFrame()
+ documentFrame = self.utilities.documentFrame()
prevObj = self.findPreviousObject(prevObj, documentFrame)
prevOffset = 0
@@ -6293,7 +5883,7 @@ class Script(default.Script):
if newX1 < oldX <= newX2:
newObj = item[0]
newOffset = 0
- text = self.queryNonEmptyText(prevObj)
+ text = self.utilities.queryNonEmptyText(prevObj)
if text:
newY = extents[1] + extents[3] / 2
newOffset = text.getOffsetAtPoint(oldX, newY, 0)
@@ -6371,7 +5961,7 @@ class Script(default.Script):
if currentLine == nextLine:
#print "find next line still stuck", nextObj, nextOffset
- documentFrame = self.getDocumentFrame()
+ documentFrame = self.utilities.documentFrame()
nextObj = self.findNextObject(nextObj, documentFrame)
nextOffset = 0
@@ -6404,7 +5994,7 @@ class Script(default.Script):
if newX1 < oldX <= newX2:
newObj = item[0]
newOffset = 0
- text = self.queryNonEmptyText(nextObj)
+ text = self.utilities.queryNonEmptyText(nextObj)
if text:
newY = extents[1] + extents[3] / 2
newOffset = text.getOffsetAtPoint(oldX, newY, 0)
@@ -6512,9 +6102,8 @@ class Script(default.Script):
[obj, characterOffset] = self.getCaretContext()
comboBox = None
if obj.getRole() == pyatspi.ROLE_MENU_ITEM:
- comboBox = self.getAncestor(obj,
- [pyatspi.ROLE_COMBO_BOX],
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ comboBox = self.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_COMBO_BOX], [pyatspi.ROLE_DOCUMENT_FRAME])
else:
index = self.getChildIndex(obj, characterOffset)
if index >= 0:
@@ -6546,7 +6135,7 @@ class Script(default.Script):
#
if obj and obj.getState().contains(pyatspi.STATE_SELECTABLE):
obj = obj.parent.parent
- characterOffset = self.getCharacterOffsetInParent(obj)
+ characterOffset = self.utilities.characterOffsetInParent(obj)
self.currentLineContents = None
characterOffset = max(0, characterOffset)
@@ -6561,7 +6150,7 @@ class Script(default.Script):
while line and not found:
index = self.findObjectOnLine(prevObj, prevOffset, useful)
- if not self.isSameObject(obj, prevObj):
+ if not self.utilities.isSameObject(obj, prevObj):
# The question is, have we found the beginning of this
# object? If the offset is 0 or there's more than one
# object on this line and we started on a later line,
@@ -6586,7 +6175,7 @@ class Script(default.Script):
if not found:
mayHaveGoneTooFar = True
- elif self.isSameObject(obj, prevObj) \
+ elif self.utilities.isSameObject(obj, prevObj) \
and 0 == prevOffset < characterOffset:
found = True
@@ -6617,7 +6206,7 @@ class Script(default.Script):
found = not (prevObj is None)
elif mayHaveGoneTooFar and self._nextLineContents:
- if not self.isSameObject(obj, prevObj):
+ if not self.utilities.isSameObject(obj, prevObj):
prevObj = useful[index][0]
prevOffset = useful[index][1]
@@ -6639,7 +6228,7 @@ class Script(default.Script):
#
if obj and obj.getState().contains(pyatspi.STATE_SELECTABLE):
obj = obj.parent.parent
- characterOffset = self.getCharacterOffsetInParent(obj)
+ characterOffset = self.utilities.characterOffsetInParent(obj)
self.currentLineContents = None
characterOffset = max(0, characterOffset)
@@ -6652,7 +6241,7 @@ class Script(default.Script):
while line and not found:
useful = self.getMeaningfulObjectsFromLine(line)
index = self.findObjectOnLine(nextObj, nextOffset, useful)
- if not self.isSameObject(obj, nextObj):
+ if not self.utilities.isSameObject(obj, nextObj):
nextObj = useful[0][0]
nextOffset = useful[0][1]
found = True
diff --git a/src/orca/scripts/toolkits/Gecko/script_utilities.py b/src/orca/scripts/toolkits/Gecko/script_utilities.py
new file mode 100644
index 0000000..b189591
--- /dev/null
+++ b/src/orca/scripts/toolkits/Gecko/script_utilities.py
@@ -0,0 +1,457 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Commonly-required utility methods needed by -- and potentially
+ customized by -- application and toolkit scripts. They have
+ been pulled out from the scripts because certain scripts had
+ gotten way too large as a result of including these methods."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__ = "LGPL"
+
+import pyatspi
+
+import orca.debug as debug
+import orca.orca_state as orca_state
+import orca.script_utilities as script_utilities
+
+#############################################################################
+# #
+# Utilities #
+# #
+#############################################################################
+
+class Utilities(script_utilities.Utilities):
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ script_utilities.Utilities.__init__(self, script)
+
+ #########################################################################
+ # #
+ # Utilities for finding, identifying, and comparing accessibles #
+ # #
+ #########################################################################
+
+ def cellIndex(self, obj):
+ """Returns the index of the cell which should be used with the
+ table interface. This is necessary because we cannot count on
+ the index we need being the same as the index in the parent.
+ See, for example, tables with captions and tables with rows
+ that have attributes."""
+
+ index = -1
+ parent = self.ancestorWithRole(obj,
+ [pyatspi.ROLE_TABLE,
+ pyatspi.ROLE_TREE_TABLE,
+ pyatspi.ROLE_TREE],
+ [pyatspi.ROLE_DOCUMENT_FRAME])
+ try:
+ table = parent.queryTable()
+ except:
+ pass
+ else:
+ attrs = dict([attr.split(':', 1) for attr in obj.getAttributes()])
+ index = attrs.get('table-cell-index')
+ if index:
+ index = int(index)
+ else:
+ index = obj.getIndexInParent()
+
+ return index
+
+ def displayedText(self, obj):
+ """Returns the text being displayed for an object.
+
+ Arguments:
+ - obj: the object
+
+ Returns the text being displayed for an object or None if there isn't
+ any text being shown. Overridden in this script because we have lots
+ of whitespace we need to remove.
+ """
+
+ displayedText = script_utilities.Utilities.displayedText(self, obj)
+ if displayedText \
+ and not (obj.getState().contains(pyatspi.STATE_EDITABLE) \
+ or obj.getRole() in [pyatspi.ROLE_ENTRY,
+ pyatspi.ROLE_PASSWORD_TEXT]):
+ displayedText = displayedText.strip()
+ # Some ARIA widgets (e.g. the list items in the chat box
+ # in gmail) implement the accessible text interface but
+ # only contain whitespace.
+ #
+ if not displayedText \
+ and obj.getState().contains(pyatspi.STATE_FOCUSED):
+ label = self.displayedLabel(obj)
+ if not label:
+ displayedText = obj.name
+
+ return displayedText
+
+ def displayedLabel(self, obj):
+ """If there is an object labelling the given object, return the
+ text being displayed for the object labelling this object.
+ Otherwise, return None. Overridden here to handle instances
+ of bogus labels and form fields where a lack of labels necessitates
+ our attempt to guess the text that is functioning as a label.
+
+ Argument:
+ - obj: the object in question
+
+ Returns the string of the object labelling this object, or None
+ if there is nothing of interest here.
+ """
+
+ string = None
+ labels = self.labelsForObject(obj)
+ for label in labels:
+ # Check to see if the official labels are valid.
+ #
+ bogus = False
+ if self._script.inDocumentContent() \
+ and obj.getRole() in [pyatspi.ROLE_COMBO_BOX,
+ pyatspi.ROLE_LIST]:
+ # Bogus case #1:
+ # <label></label> surrounding the entire combo box/list which
+ # makes the entire combo box's/list's contents serve as the
+ # label. We can identify this case because the child of the
+ # label is the combo box/list. See bug #428114, #441476.
+ #
+ if label.childCount:
+ bogus = (label[0].getRole() == obj.getRole())
+
+ if not bogus:
+ # Bogus case #2:
+ # <label></label> surrounds not just the text serving as the
+ # label, but whitespace characters as well (e.g. the text
+ # serving as the label is on its own line within the HTML).
+ # Because of the Mozilla whitespace bug, these characters
+ # will become part of the label which will cause the label
+ # and name to no longer match and Orca to seemingly repeat
+ # the label. Therefore, strip out surrounding whitespace.
+ # See bug #441610 and
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=348901
+ #
+ expandedLabel = self.expandEOCs(label)
+ if expandedLabel:
+ string = self.appendString(string, expandedLabel.strip())
+
+ return string
+
+ def documentFrame(self):
+ """Returns the document frame that holds the content being shown."""
+
+ # [[[TODO: WDW - this is based upon the 12-Oct-2006 implementation
+ # that uses the EMBEDS relation on the top level frame as a means
+ # to find the document frame. Future implementations will break
+ # this.]]]
+ #
+ documentFrame = None
+ for child in self._script.app:
+ if child.getRole() == pyatspi.ROLE_FRAME:
+ relationSet = child.getRelationSet()
+ for relation in relationSet:
+ if relation.getRelationType() \
+ == pyatspi.RELATION_EMBEDS:
+ documentFrame = relation.getTarget(0)
+ if documentFrame.getState().contains(
+ pyatspi.STATE_SHOWING):
+ break
+ else:
+ documentFrame = None
+
+ # Certain add-ons can interfere with the above approach. But we
+ # should have a locusOfFocus. If so look up and try to find the
+ # document frame. See bug 537303.
+ #
+ if not documentFrame:
+ documentFrame = self.ancestorWithRole(
+ orca_state.locusOfFocus,
+ [pyatspi.ROLE_DOCUMENT_FRAME],
+ [pyatspi.ROLE_FRAME])
+
+ return documentFrame
+
+ def documentFrameURI(self):
+ """Returns the URI of the document frame that is active."""
+
+ documentFrame = self.documentFrame()
+ if documentFrame:
+ # If the document frame belongs to a Thunderbird message which
+ # has just been deleted, getAttributes() will crash Thunderbird.
+ #
+ if not documentFrame.getState().contains(pyatspi.STATE_DEFUNCT):
+ attrs = documentFrame.queryDocument().getAttributes()
+ for attr in attrs:
+ if attr.startswith('DocURL'):
+ return attr[7:]
+
+ return None
+
+ def isLayoutOnly(self, obj):
+ """Returns True if the given object is for layout purposes only."""
+
+ if self._script.isUselessObject(obj):
+ debug.println(debug.LEVEL_FINEST,
+ "Object deemed to be useless: %s" % obj)
+ return True
+
+ else:
+ return script_utilities.Utilities.isLayoutOnly(self, obj)
+
+ def isReadOnlyTextArea(self, obj):
+ """Returns True if obj is a text entry area that is read only."""
+
+ if not obj.getRole() == pyatspi.ROLE_ENTRY:
+ return False
+
+ state = obj.getState()
+ readOnly = state.contains(pyatspi.STATE_FOCUSABLE) \
+ and not state.contains(pyatspi.STATE_EDITABLE)
+ debug.println(debug.LEVEL_ALL,
+ "Gecko - isReadOnlyTextArea=%s for %s" \
+ % (readOnly, debug.getAccessibleDetails(obj)))
+
+ return readOnly
+
+ def nodeLevel(self, obj):
+ """ Determines the level of at which this object is at by using
+ the object attribute 'level'. To be consistent with the default
+ nodeLevel() this value is 0-based (Gecko return is 1-based) """
+
+ if obj is None or obj.getRole() == pyatspi.ROLE_HEADING \
+ or (obj.parent and obj.parent.getRole() == pyatspi.ROLE_MENU):
+ return -1
+
+ try:
+ state = obj.getState()
+ except:
+ return -1
+ else:
+ if state.contains(pyatspi.STATE_DEFUNCT):
+ # Yelp (or perhaps the work-in-progress a11y patch)
+ # seems to be guilty of this.
+ #
+ #print "nodeLevel - obj is defunct", obj
+ debug.println(debug.LEVEL_WARNING,
+ "nodeLevel - obj is defunct")
+ debug.printStack(debug.LEVEL_WARNING)
+ return -1
+
+ attrs = obj.getAttributes()
+ if attrs is None:
+ return -1
+ for attr in attrs:
+ if attr.startswith("level:"):
+ return int(attr[6:]) - 1
+ return -1
+
+ def showingDescendants(self, parent):
+ """Given an accessible object, returns a list of accessible children
+ that are actually showing/visible/pursable for flat review. We're
+ overriding the default method here primarily to handle enormous
+ tree tables (such as the Thunderbird message list) which do not
+ manage their descendants.
+
+ Arguments:
+ - parent: The accessible which manages its descendants
+
+ Returns a list of Accessible descendants which are showing.
+ """
+
+ if not parent:
+ return []
+
+ # If this object is not a tree table, if it manages its descendants,
+ # or if it doesn't have very many children, let the default script
+ # handle it.
+ #
+ if parent.getRole() != pyatspi.ROLE_TREE_TABLE \
+ or parent.getState().contains(pyatspi.STATE_MANAGES_DESCENDANTS) \
+ or parent.childCount <= 50:
+ return script_utilities.Utilities.showingDescendants(self, parent)
+
+ try:
+ table = parent.queryTable()
+ except NotImplementedError:
+ return []
+
+ descendants = []
+
+ # First figure out what columns are visible as there's no point
+ # in examining cells which we know won't be visible.
+ #
+ visibleColumns = []
+ for i in range(table.nColumns):
+ header = table.getColumnHeader(i)
+ if self.pursueForFlatReview(header):
+ visibleColumns.append(i)
+ descendants.append(header)
+
+ if not len(visibleColumns):
+ return []
+
+ # Now that we know in which columns we can expect to find visible
+ # cells, try to quickly locate a visible row.
+ #
+ startingRow = 0
+
+ # If we have one or more selected items, odds are fairly good
+ # (although not guaranteed) that one of those items happens to
+ # be showing. Failing that, calculate how many rows can fit in
+ # the exposed portion of the tree table and scroll down.
+ #
+ selectedRows = table.getSelectedRows()
+ for row in selectedRows:
+ acc = table.getAccessibleAt(row, visibleColumns[0])
+ if self.pursueForFlatReview(acc):
+ startingRow = row
+ break
+ else:
+ try:
+ tableExtents = parent.queryComponent().getExtents(0)
+ acc = table.getAccessibleAt(0, visibleColumns[0])
+ cellExtents = acc.queryComponent().getExtents(0)
+ except:
+ pass
+ else:
+ rowIncrement = max(1, tableExtents.height / cellExtents.height)
+ for row in range(0, table.nRows, rowIncrement):
+ acc = table.getAccessibleAt(row, visibleColumns[0])
+ if acc and self.pursueForFlatReview(acc):
+ startingRow = row
+ break
+
+ # Get everything after this point which is visible.
+ #
+ for row in range(startingRow, table.nRows):
+ acc = table.getAccessibleAt(row, visibleColumns[0])
+ if self.pursueForFlatReview(acc):
+ descendants.append(acc)
+ for col in visibleColumns[1:len(visibleColumns)]:
+ descendants.append(table.getAccessibleAt(row, col))
+ else:
+ break
+
+ # Get everything before this point which is visible.
+ #
+ for row in range(startingRow - 1, -1, -1):
+ acc = table.getAccessibleAt(row, visibleColumns[0])
+ if self.pursueForFlatReview(acc):
+ thisRow = [acc]
+ for col in visibleColumns[1:len(visibleColumns)]:
+ thisRow.append(table.getAccessibleAt(row, col))
+ descendants[0:0] = thisRow
+ else:
+ break
+
+ return descendants
+
+ def uri(self, obj):
+ """Return the URI for a given link object.
+
+ Arguments:
+ - obj: the Accessible object.
+ """
+
+ # Getting a link's URI requires a little workaround due to
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=379747. You
+ # should be able to use getURI() directly on the link but
+ # instead must use ihypertext.getLink(0) on parent then use
+ # getURI on returned ihyperlink.
+ try:
+ ihyperlink = obj.parent.queryHypertext().getLink(0)
+ except:
+ return None
+ else:
+ try:
+ return ihyperlink.getURI(0)
+ except:
+ return None
+
+ #########################################################################
+ # #
+ # Utilities for working with the accessible text interface #
+ # #
+ #########################################################################
+
+ def isWordMisspelled(self, obj, offset):
+ """Identifies if the current word is flagged as misspelled by the
+ application.
+
+ Arguments:
+ - obj: An accessible which implements the accessible text interface.
+ - offset: Offset in the accessible's text for which to retrieve the
+ attributes.
+
+ Returns True if the word is flagged as misspelled.
+ """
+
+ attributes, start, end = self.textAttributes(obj, offset, True)
+ error = attributes.get("invalid")
+
+ return error == "spelling"
+
+ def setCaretOffset(self, obj, characterOffset):
+ self._script.setCaretPosition(obj, characterOffset)
+ self._script.updateBraille(obj)
+
+ def textAttributes(self, acc, offset, get_defaults=False):
+ """Get the text attributes run for a given offset in a given accessible
+
+ Arguments:
+ - acc: An accessible.
+ - offset: Offset in the accessible's text for which to retrieve the
+ attributes.
+ - get_defaults: Get the default attributes as well as the unique ones.
+ Default is True
+
+ Returns a dictionary of attributes, a start offset where the attributes
+ begin, and an end offset. Returns ({}, 0, 0) if the accessible does not
+ supprt the text attribute.
+ """
+
+ # For really large objects, a call to getAttributes can take up to
+ # two seconds! This is a Gecko bug. We'll try to improve things
+ # by storing attributes.
+ #
+ attrsForObj = self._script.currentAttrs.get(hash(acc)) or {}
+ if attrsForObj.has_key(offset):
+ return attrsForObj.get(offset)
+
+ attrs = script_utilities.Utilities.textAttributes(
+ self, acc, offset, get_defaults)
+ self._script.currentAttrs[hash(acc)] = {offset:attrs}
+
+ return attrs
+
+ #########################################################################
+ # #
+ # Miscellaneous Utilities #
+ # #
+ #########################################################################
diff --git a/src/orca/scripts/toolkits/Gecko/speech_generator.py b/src/orca/scripts/toolkits/Gecko/speech_generator.py
index 1344376..18355e3 100644
--- a/src/orca/scripts/toolkits/Gecko/speech_generator.py
+++ b/src/orca/scripts/toolkits/Gecko/speech_generator.py
@@ -91,15 +91,14 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
result.extend(speech_generator.SpeechGenerator._generateName(
self, obj, **args))
if not result and role == pyatspi.ROLE_LIST_ITEM:
- result.append(self._script.expandEOCs(obj))
+ result.append(self._script.utilities.expandEOCs(obj))
link = None
if role == pyatspi.ROLE_LINK:
link = obj
elif role == pyatspi.ROLE_IMAGE and not result:
- link = self._script.getAncestor(obj,
- [pyatspi.ROLE_LINK],
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ link = self._script.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_LINK], [pyatspi.ROLE_DOCUMENT_FRAME])
if link and (not result or len(result[0].strip()) == 0):
# If there's no text for the link, expose part of the
# URI to the user.
@@ -173,7 +172,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
# we need to add it to utterances.
#
if role == pyatspi.ROLE_RADIO_BUTTON \
- and self._script.getDisplayedLabel(obj):
+ and self._script.utilities.displayedLabel(obj):
pass
else:
result.extend(
@@ -206,9 +205,8 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
# navigating in the combo box)
#
if not obj.getState().contains(pyatspi.STATE_FOCUSED):
- comboBox = self._script.getAncestor(obj,
- [pyatspi.ROLE_COMBO_BOX],
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ comboBox = self._script.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_COMBO_BOX], [pyatspi.ROLE_DOCUMENT_FRAME])
if comboBox:
return self._generateRoleName(comboBox, **args)
@@ -233,9 +231,8 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
if not (role in doNotSpeak):
if role == pyatspi.ROLE_IMAGE:
- link = self._script.getAncestor(obj,
- [pyatspi.ROLE_LINK],
- [pyatspi.ROLE_DOCUMENT_FRAME])
+ link = self._script.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_LINK], [pyatspi.ROLE_DOCUMENT_FRAME])
if link:
result.append(rolenames.getSpeechForRoleName(link))
@@ -267,7 +264,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
def _generateExpandedEOCs(self, obj, **args):
"""Returns the expanded embedded object characters for an object."""
result = []
- text = self._script.expandEOCs(obj)
+ text = self._script.utilities.expandEOCs(obj)
if text:
result.append(text)
return result
@@ -307,7 +304,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
def _generateAncestors(self, obj, **args):
result = []
priorObj = args.get('priorObj', None)
- commonAncestor = self._script.findCommonAncestor(priorObj, obj)
+ commonAncestor = self._script.utilities.commonAncestor(priorObj, obj)
if obj is commonAncestor:
return result
@@ -316,7 +313,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
# children, and autocompletes. (With autocompletes, we
# wind up speaking the text object). Beginning with Firefox
# 3.2, list items have names corresponding with their text.
- # This results in getDisplayedText returning actual text
+ # This results in displayedText returning actual text
# and the parent list item being spoken when it should not
# be. So we'll take list items out of the context.
#
@@ -348,11 +345,11 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
parent = obj.parent
while parent and (parent.parent != parent):
role = parent.getRole()
- if self._script.isSameObject(parent, commonAncestor) \
+ if self._script.utilities.isSameObject(parent, commonAncestor) \
or role in stopRoles:
break
- if role in skipRoles or self._script.isLayoutOnly(parent):
+ if role in skipRoles or self._script.utilities.isLayoutOnly(parent):
parent = parent.parent
continue
@@ -365,18 +362,18 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
continue
# Also skip the parent if its accessible text is a single
- # EMBEDDED_OBJECT_CHARACTER: Script.getDisplayedText will
- # end up coming back to the child of an object for the text
- # if an object's text contains a single EOC. In addition,
+ # EMBEDDED_OBJECT_CHARACTER: displayedText will end up
+ # coming back to the child of an object for the text if
+ # an object's text contains a single EOC. In addition,
# beginning with Firefox 3.2, a table cell may derive its
# accessible name from focusable objects it contains (e.g.
- # links, form fields). getDisplayedText will return the
+ # links, form fields). displayedText will return the
# object's name in this case (because of the presence of
# the EOC and other characters). This causes us to be
# chatty. So if it's a table cell which contains an EOC,
# we will also skip the parent.
#
- parentText = self._script.queryNonEmptyText(parent)
+ parentText = self._script.utilities.queryNonEmptyText(parent)
if parentText:
unicodeText = parentText.getText(0, -1).decode("UTF-8")
if self._script.EMBEDDED_OBJECT_CHARACTER in unicodeText \
@@ -387,8 +384,8 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
# Put in the text and label (if they exist).
#
- text = self._script.getDisplayedText(parent)
- label = self._script.getDisplayedLabel(parent)
+ text = self._script.utilities.displayedText(parent)
+ label = self._script.utilities.displayedLabel(parent)
newResult = []
if text and (text != label) and len(text.strip()) \
and (not text.startswith("chrome://")):
diff --git a/src/orca/scripts/toolkits/Gecko/structural_navigation.py b/src/orca/scripts/toolkits/Gecko/structural_navigation.py
index dccd53f..8bd1298 100644
--- a/src/orca/scripts/toolkits/Gecko/structural_navigation.py
+++ b/src/orca/scripts/toolkits/Gecko/structural_navigation.py
@@ -99,7 +99,7 @@ class GeckoStructuralNavigation(structural_navigation.StructuralNavigation):
interest is contained.
"""
- return self._script.getDocumentFrame()
+ return self._script.utilities.documentFrame()
def _isInDocument(self, obj):
"""Returns True of the object is inside of the document."""
diff --git a/src/orca/scripts/toolkits/J2SE-access-bridge/Makefile.am b/src/orca/scripts/toolkits/J2SE-access-bridge/Makefile.am
index 61cfcc3..36bccf1 100644
--- a/src/orca/scripts/toolkits/J2SE-access-bridge/Makefile.am
+++ b/src/orca/scripts/toolkits/J2SE-access-bridge/Makefile.am
@@ -4,6 +4,7 @@ orca_python_PYTHON = \
__init__.py \
formatting.py \
script.py \
+ script_utilities.py \
speech_generator.py
orca_pythondir=$(pyexecdir)/orca/scripts/toolkits/J2SE-access-bridge
diff --git a/src/orca/scripts/toolkits/J2SE-access-bridge/script.py b/src/orca/scripts/toolkits/J2SE-access-bridge/script.py
index 3c2cccb..4406eff 100644
--- a/src/orca/scripts/toolkits/J2SE-access-bridge/script.py
+++ b/src/orca/scripts/toolkits/J2SE-access-bridge/script.py
@@ -32,6 +32,7 @@ import orca.input_event as input_event
import orca.orca as orca
import orca.orca_state as orca_state
+from script_utilities import Utilities
from speech_generator import SpeechGenerator
from formatting import Formatting
@@ -61,14 +62,17 @@ class Script(default.Script):
self.lastDescendantChangedSource = None
def getSpeechGenerator(self):
- """Returns the speech generator for this script.
- """
+ """Returns the speech generator for this script."""
return SpeechGenerator(self)
def getFormatting(self):
"""Returns the formatting strings for this script."""
return Formatting(self)
+ def getUtilities(self):
+ """Returns the utilites for this script."""
+ return Utilities(self)
+
def checkKeyboardEventData(self, keyboardEvent):
"""Checks the data on the keyboard event.
@@ -110,86 +114,6 @@ class Script(default.Script):
if 0 < keyval < 256:
keyboardEvent.event_string = unichr(keyval).encode("UTF-8")
- def getValidObj(self, rootObj, obj, onlyShowing=True):
- """Attempts to convert an older copy of an accessible into the
- current, active version. We need to do this in order to ascend
- the hierarchy.
-
- Arguments:
- - rootObj: the top-most ancestor of interest
- - obj: the old object we're attempting to replace
- - onlyShowing: whether or not we should limit matches to those
- which have STATE_SHOWING
-
- Returns an accessible replacement for obj if one can be found;
- otherwise, None.
- """
-
- if not (obj and rootObj):
- return None
-
- items = self.findByRole(rootObj, obj.getRole(), onlyShowing)
- for item in items:
- if item.name == obj.name and self.isSameObject(item, obj):
- return item
-
- return None
-
- def getNodeLevel(self, obj):
- """Determines the node level of this object if it is in a tree
- relation, with 0 being the top level node. If this object is
- not in a tree relation, then -1 will be returned.
-
- Arguments:
- -obj: the Accessible object
- """
-
- newObj = self.getValidObj(self.lastDescendantChangedSource, obj)
- if not newObj:
- return default.Script.getNodeLevel(self, obj)
-
- count = 0
- while newObj:
- state = newObj.getState()
- if state.contains(pyatspi.STATE_EXPANDABLE) \
- or state.contains(pyatspi.STATE_COLLAPSED):
- if state.contains(pyatspi.STATE_VISIBLE):
- count += 1
- newObj = newObj.parent
- else:
- break
-
- return count - 1
-
- def isSameObject(self, obj1, obj2):
- """Compares two objects to determine if they are functionally
- the same object. This is needed because some applications and
- toolkits kill and replace accessibles."""
-
- if (obj1 == obj2):
- return True
- elif (not obj1) or (not obj2):
- return False
- elif (obj1.name != obj2.name) or (obj1.childCount != obj2.childCount):
- return False
-
- # This is to handle labels in trees. In some cases the default
- # script's method gives us false positives; other times false
- # negatives.
- #
- if obj1.getRole() == obj2.getRole() == pyatspi.ROLE_LABEL:
- try:
- ext1 = obj1.queryComponent().getExtents(0)
- ext2 = obj2.queryComponent().getExtents(0)
- except:
- pass
- else:
- if ext1.x == ext2.x and ext1.y == ext2.y \
- and ext1.width == ext2.width and ext1.height == ext2.height:
- return True
-
- return default.Script.isSameObject(self, obj1, obj2)
-
def onFocus(self, event):
"""Called whenever an object gets focus.
@@ -249,9 +173,8 @@ class Script(default.Script):
return
if event.source.getRole() == pyatspi.ROLE_LIST:
- combobox = self.getAncestor(event.source,
- [pyatspi.ROLE_COMBO_BOX],
- [pyatspi.ROLE_PANEL])
+ combobox = self.utilities.ancestorWithRole(
+ event.source, [pyatspi.ROLE_COMBO_BOX], [pyatspi.ROLE_PANEL])
if combobox:
orca.visualAppearanceChanged(event, combobox)
return
@@ -268,10 +191,10 @@ class Script(default.Script):
# ignore caret movement events caused by value changes and
# just process the single value changed event.
#
- isSpinBox = self.isDesiredFocusedItem(event.source,
- [pyatspi.ROLE_TEXT,
- pyatspi.ROLE_PANEL,
- pyatspi.ROLE_SPIN_BUTTON])
+ isSpinBox = self.utilities.hasMatchingHierarchy(
+ event.source, [pyatspi.ROLE_TEXT,
+ pyatspi.ROLE_PANEL,
+ pyatspi.ROLE_SPIN_BUTTON])
if isSpinBox:
if isinstance(orca_state.lastInputEvent,
input_event.KeyboardEvent):
@@ -294,8 +217,9 @@ class Script(default.Script):
# Avoid doing this with objects that manage their descendants
# because they'll issue a descendant changed event. (Note: This
- # equality check is intentional; isSameObject() is especially
- # thorough with trees and tables, which is not performant.
+ # equality check is intentional; utilities.isSameObject() is
+ # especially thorough with trees and tables, which is not
+ # performant.
#
if event.source == self.lastDescendantChangedSource:
return
@@ -347,12 +271,12 @@ class Script(default.Script):
for child in event.source:
# search the layered pane for a popup menu
if child.getRole() == pyatspi.ROLE_LAYERED_PANE:
- popup = self.findByRole(child,
- pyatspi.ROLE_POPUP_MENU, False)
+ popup = self.utilities.descendantsWithRole(
+ child, pyatspi.ROLE_POPUP_MENU, False)
if len(popup) > 0:
# set the locus of focus to the armed menu item
- items = self.findByRole(popup[0],
- pyatspi.ROLE_MENU_ITEM, False)
+ items = self.utilities.descendantsWithRole(
+ popup[0], pyatspi.ROLE_MENU_ITEM, False)
for item in items:
if item.getState().contains(pyatspi.STATE_ARMED):
orca.setLocusOfFocus(event, item)
diff --git a/src/orca/scripts/toolkits/J2SE-access-bridge/script_utilities.py b/src/orca/scripts/toolkits/J2SE-access-bridge/script_utilities.py
new file mode 100644
index 0000000..240d6d7
--- /dev/null
+++ b/src/orca/scripts/toolkits/J2SE-access-bridge/script_utilities.py
@@ -0,0 +1,151 @@
+# Orca
+#
+# Copyright 2010 Joanmarie Diggs.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
+# Boston MA 02110-1301 USA.
+
+"""Commonly-required utility methods needed by -- and potentially
+ customized by -- application and toolkit scripts. They have
+ been pulled out from the scripts because certain scripts had
+ gotten way too large as a result of including these methods."""
+
+__id__ = "$Id$"
+__version__ = "$Revision$"
+__date__ = "$Date$"
+__copyright__ = "Copyright (c) 2010 Joanmarie Diggs."
+__license__ = "LGPL"
+
+import pyatspi
+
+import orca.script_utilities as script_utilities
+
+#############################################################################
+# #
+# Utilities #
+# #
+#############################################################################
+
+class Utilities(script_utilities.Utilities):
+
+ def __init__(self, script):
+ """Creates an instance of the Utilities class.
+
+ Arguments:
+ - script: the script with which this instance is associated.
+ """
+
+ script_utilities.Utilities.__init__(self, script)
+
+ #########################################################################
+ # #
+ # Utilities for finding, identifying, and comparing accessibles #
+ # #
+ #########################################################################
+
+ def isSameObject(self, obj1, obj2):
+ """Compares two objects to determine if they are functionally
+ the same object. This is needed because some applications and
+ toolkits kill and replace accessibles."""
+
+ if (obj1 == obj2):
+ return True
+ elif (not obj1) or (not obj2):
+ return False
+ elif (obj1.name != obj2.name) or (obj1.childCount != obj2.childCount):
+ return False
+
+ # This is to handle labels in trees. In some cases the default
+ # script's method gives us false positives; other times false
+ # negatives.
+ #
+ if obj1.getRole() == obj2.getRole() == pyatspi.ROLE_LABEL:
+ try:
+ ext1 = obj1.queryComponent().getExtents(0)
+ ext2 = obj2.queryComponent().getExtents(0)
+ except:
+ pass
+ else:
+ if ext1.x == ext2.x and ext1.y == ext2.y \
+ and ext1.width == ext2.width and ext1.height == ext2.height:
+ return True
+
+ return script_utilities.Utilities.isSameObject(self, obj1, obj2)
+
+ def nodeLevel(self, obj):
+ """Determines the node level of this object if it is in a tree
+ relation, with 0 being the top level node. If this object is
+ not in a tree relation, then -1 will be returned.
+
+ Arguments:
+ -obj: the Accessible object
+ """
+
+ newObj = self.validObj(self._script.lastDescendantChangedSource, obj)
+ if not newObj:
+ return script_utilities.Utilities.nodeLevel(self, obj)
+
+ count = 0
+ while newObj:
+ state = newObj.getState()
+ if state.contains(pyatspi.STATE_EXPANDABLE) \
+ or state.contains(pyatspi.STATE_COLLAPSED):
+ if state.contains(pyatspi.STATE_VISIBLE):
+ count += 1
+ newObj = newObj.parent
+ else:
+ break
+
+ return count - 1
+
+ def validObj(self, rootObj, obj, onlyShowing=True):
+ """Attempts to convert an older copy of an accessible into the
+ current, active version. We need to do this in order to ascend
+ the hierarchy.
+
+ Arguments:
+ - rootObj: the top-most ancestor of interest
+ - obj: the old object we're attempting to replace
+ - onlyShowing: whether or not we should limit matches to those
+ which have STATE_SHOWING
+
+ Returns an accessible replacement for obj if one can be found;
+ otherwise, None.
+ """
+
+ if not (obj and rootObj):
+ return None
+
+ items = self.descendantsWithRole(rootObj, obj.getRole(), onlyShowing)
+ for item in items:
+ if item.name == obj.name \
+ and self.isSameObject(item, obj):
+ return item
+
+ return None
+
+ #########################################################################
+ # #
+ # Utilities for working with the accessible text interface #
+ # #
+ #########################################################################
+
+
+
+ #########################################################################
+ # #
+ # Miscellaneous Utilities #
+ # #
+ #########################################################################
diff --git a/src/orca/scripts/toolkits/J2SE-access-bridge/speech_generator.py b/src/orca/scripts/toolkits/J2SE-access-bridge/speech_generator.py
index e999dbc..ef871cd 100644
--- a/src/orca/scripts/toolkits/J2SE-access-bridge/speech_generator.py
+++ b/src/orca/scripts/toolkits/J2SE-access-bridge/speech_generator.py
@@ -47,9 +47,8 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
def _generateAncestors(self, obj, **args):
"""The Swing toolkit has labelled panels that do not implement the
- AccessibleText interface, but getDisplayedText returns a
- meaningful string that needs to be used if getDisplayedLabel
- returns None.
+ AccessibleText interface, but displayedText returns a meaningful
+ string that needs to be used if displayedLabel returns None.
"""
args['requireText'] = False
result = speech_generator.SpeechGenerator._generateAncestors(
@@ -108,7 +107,8 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
listObj = None
if obj and obj.getRole() == pyatspi.ROLE_COMBO_BOX:
- allLists = self._script.findByRole(obj, pyatspi.ROLE_LIST, False)
+ allLists = self._script.utilities.descendantsWithRole(
+ obj, pyatspi.ROLE_LIST, False)
if len(allLists) == 1:
listObj = allLists[0]
@@ -145,9 +145,8 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
result = []
if args.get('formatType', 'unfocused') == 'basicWhereAmI' \
and obj.getRole() == pyatspi.ROLE_TEXT:
- spinbox = self._script.getAncestor(obj,
- [pyatspi.ROLE_SPIN_BUTTON],
- None)
+ spinbox = self._script.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_SPIN_BUTTON], None)
if spinbox:
obj = spinbox
result.extend(speech_generator.SpeechGenerator.\
diff --git a/src/orca/speech.py b/src/orca/speech.py
index 21cc619..7a02fe8 100644
--- a/src/orca/speech.py
+++ b/src/orca/speech.py
@@ -159,7 +159,7 @@ def _speak(text, acss, interrupt):
if settings.speakMultiCaseStringsAsWords:
text = _processMultiCaseString(text)
if orca_state.activeScript and orca_state.usePronunciationDictionary:
- text = orca_state.activeScript.adjustForPronunciation(text)
+ text = orca_state.activeScript.utilities.adjustForPronunciation(text)
if settings.speakMultiCaseStringsAsWords:
text = _processMultiCaseString(text)
diff --git a/src/orca/speech_generator.py b/src/orca/speech_generator.py
index 5f979ac..5196601 100644
--- a/src/orca/speech_generator.py
+++ b/src/orca/speech_generator.py
@@ -153,7 +153,7 @@ class SpeechGenerator(generator.Generator):
hierarchy and which are not in a label for or labelled by
relation.
"""
- labels = self._script.findUnrelatedLabels(obj)
+ labels = self._script.utilities.unrelatedLabels(obj)
result = []
for label in labels:
name = self._generateName(label, **args)
@@ -212,7 +212,7 @@ class SpeechGenerator(generator.Generator):
# URI is returned as a tuple containing six components:
# scheme://netloc/path;parameters?query#fragment.
#
- link_uri = self._script.getURI(obj)
+ link_uri = self._script.utilities.uri(obj)
if not link_uri:
# [[[TODO - JD: For some reason, this is failing for certain
# links. The current whereAmI code says, "It might be an anchor.
@@ -223,7 +223,7 @@ class SpeechGenerator(generator.Generator):
#
result.extend(self._generateLabel(obj))
result.extend(self._generateRoleName(obj))
- result.append(self._script.getDisplayedText(obj))
+ result.append(self._script.utilities.displayedText(obj))
else:
link_uri_info = urlparse.urlparse(link_uri)
if link_uri_info[0] in ["ftp", "ftps", "file"]:
@@ -240,7 +240,7 @@ class SpeechGenerator(generator.Generator):
# Translators: this is the protocol of a link eg. http, mailto.
#
linkOutput = _("%s link") % link_uri_info[0]
- text = self._script.getDisplayedText(obj)
+ text = self._script.utilities.displayedText(obj)
if not text:
# If there's no text for the link, expose part of the
# URI to the user.
@@ -259,12 +259,12 @@ class SpeechGenerator(generator.Generator):
pointed to by the URI of the link associated with obj.
"""
result = []
- link_uri = self._script.getURI(obj)
+ link_uri = self._script.utilities.uri(obj)
if link_uri:
link_uri_info = urlparse.urlparse(link_uri)
else:
return result
- doc_uri = self._script.getDocumentFrameURI()
+ doc_uri = self._script.utilities.documentFrameURI()
if doc_uri:
doc_uri_info = urlparse.urlparse(doc_uri)
if link_uri_info[1] == doc_uri_info[1]:
@@ -308,7 +308,7 @@ class SpeechGenerator(generator.Generator):
"""
result = []
sizeString = ""
- uri = self._script.getURI(obj)
+ uri = self._script.utilities.uri(obj)
if not uri:
return result
try:
@@ -389,7 +389,7 @@ class SpeechGenerator(generator.Generator):
if table \
and ((priorObj.getRole() == pyatspi.ROLE_TABLE_CELL) \
or (priorObj.getRole() == pyatspi.ROLE_TABLE)):
- index = self._script.getCellIndex(priorObj)
+ index = self._script.utilities.cellIndex(priorObj)
oldRow = table.getRowAtIndex(index)
else:
oldRow = -1
@@ -399,7 +399,7 @@ class SpeechGenerator(generator.Generator):
except:
pass
else:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
newRow = table.getRowAtIndex(index)
if (newRow >= 0) \
and (index != newRow) \
@@ -435,7 +435,7 @@ class SpeechGenerator(generator.Generator):
if table \
and ((priorObj.getRole() == pyatspi.ROLE_TABLE_CELL) \
or (priorObj.getRole() == pyatspi.ROLE_TABLE)):
- index = self._script.getCellIndex(priorObj)
+ index = self._script.utilities.cellIndex(priorObj)
oldCol = table.getColumnAtIndex(index)
else:
oldCol = -1
@@ -445,7 +445,7 @@ class SpeechGenerator(generator.Generator):
except:
pass
else:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
newCol = table.getColumnAtIndex(index)
if (newCol >= 0) \
and (index != newCol) \
@@ -537,7 +537,7 @@ class SpeechGenerator(generator.Generator):
if args.get('guessCoordinates', False):
col = self._script.pointOfReference.get('lastColumn', -1)
else:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
col = table.getColumnAtIndex(index)
if col >= 0:
# Translators: this is in references to a column in a
@@ -560,7 +560,7 @@ class SpeechGenerator(generator.Generator):
if args.get('guessCoordinates', False):
row = self._script.pointOfReference.get('lastRow', -1)
else:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
row = table.getRowAtIndex(index)
if row >= 0:
# Translators: this is in references to a row in a table.
@@ -583,7 +583,7 @@ class SpeechGenerator(generator.Generator):
except:
table = None
else:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
col = table.getColumnAtIndex(index)
row = table.getRowAtIndex(index)
# Translators: this is in references to a column in a
@@ -608,15 +608,14 @@ class SpeechGenerator(generator.Generator):
if obj.getRole() == pyatspi.ROLE_TABLE_CELL:
cell = obj
else:
- cell = self._script.getAncestor(obj,
- [pyatspi.ROLE_TABLE_CELL],
- [pyatspi.ROLE_FRAME])
+ cell = self._script.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_TABLE_CELL], [pyatspi.ROLE_FRAME])
try:
table = cell.parent.queryTable()
except:
pass
else:
- index = self._script.getCellIndex(cell)
+ index = self._script.utilities.cellIndex(cell)
row = table.getRowAtIndex(index)
col = table.getColumnAtIndex(index)
if row + 1 == table.nRows and col + 1 == table.nColumns:
@@ -641,11 +640,12 @@ class SpeechGenerator(generator.Generator):
"""
result = []
title = None
- frame = self._script.getFrame(obj)
+ frame = self._script.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_FRAME], [])
if frame:
title = frame.name
if not title:
- title = self._script.getDisplayedLabel(obj)
+ title = self._script.utilities.displayedLabel(obj)
result.append(title)
return result
@@ -667,14 +667,14 @@ class SpeechGenerator(generator.Generator):
attribStr = ""
defaultAttributes = text.getDefaultAttributes()
- attributesDictionary = \
- self._script.attributeStringToDictionary(defaultAttributes)
+ keyList, attributesDictionary = \
+ self._script.utilities.stringToKeysAndDict(defaultAttributes)
charAttributes = text.getAttributes(textOffset)
if charAttributes[0]:
- charDict = \
- self._script.attributeStringToDictionary(charAttributes[0])
- for key in charDict.keys():
+ keyList, charDict = \
+ self._script.utilities.stringToKeysAndDict(charAttributes[0])
+ for key in keyList:
attributesDictionary[key] = charDict[key]
if attributesDictionary:
@@ -708,7 +708,7 @@ class SpeechGenerator(generator.Generator):
# Also check to see if this is a hypertext link.
#
- if self._script.getLinkIndex(obj, textOffset) >= 0:
+ if self._script.utilities.linkIndex(obj, textOffset) >= 0:
attribStr += " "
# Translators: this indicates that this piece of
# text is a hypertext link.
@@ -742,11 +742,11 @@ class SpeechGenerator(generator.Generator):
nSelections = textObj.getNSelections()
- [current, other] = self._script.hasTextSelections(obj)
+ [current, other] = self._script.utilities.hasTextSelections(obj)
if current or other:
selected = True
[textContents, startOffset, endOffset] = \
- self._script.getAllSelectedText(obj)
+ self._script.utilities.allSelectedText(obj)
else:
# Get the line containing the caret
#
@@ -765,8 +765,9 @@ class SpeechGenerator(generator.Generator):
#
unicodeText = line.decode("UTF-8")
if self._script.EMBEDDED_OBJECT_CHARACTER in unicodeText:
- line = self._script.expandEOCs(obj, startOffset, endOffset)
- line = self._script.adjustForRepeats(line)
+ line = self._script.utilities.expandEOCs(
+ obj, startOffset, endOffset)
+ line = self._script.utilities.adjustForRepeats(line)
textContents = line
else:
char = textObj.getTextAtOffset(caretOffset,
@@ -903,8 +904,8 @@ class SpeechGenerator(generator.Generator):
focus.
"""
result = []
- oldLevel = self._script.getNodeLevel(args.get('priorObj', None))
- newLevel = self._script.getNodeLevel(obj)
+ oldLevel = self._script.utilities.nodeLevel(args.get('priorObj', None))
+ newLevel = self._script.utilities.nodeLevel(obj)
if (oldLevel != newLevel) and (newLevel >= 0):
result.extend(self._generateNodeLevel(obj, **args))
return result
@@ -976,7 +977,8 @@ class SpeechGenerator(generator.Generator):
inSameGroup = True
break
if (not inSameGroup) and radioGroupLabel:
- result.append(self._script.getDisplayedText(radioGroupLabel))
+ result.append(self._script.utilities.\
+ displayedText(radioGroupLabel))
return result
def _generateNumberOfChildren(self, obj, **args):
@@ -987,7 +989,7 @@ class SpeechGenerator(generator.Generator):
be moved to settings.py.]]]
"""
result = []
- childNodes = self._script.getChildNodes(obj)
+ childNodes = self._script.utilities.childNodes(obj)
children = len(childNodes)
if children:
# Translators: this is the number of items in a layered
@@ -1106,7 +1108,7 @@ class SpeechGenerator(generator.Generator):
# to let the user know.
#
alertAndDialogCount = \
- self._script.getUnfocusedAlertAndDialogCount(obj)
+ self._script.utilities.unfocusedAlertAndDialogCount(obj)
if alertAndDialogCount > 0:
# Translators: this tells the user how many unfocused
# alert and dialog windows that this application has.
@@ -1130,7 +1132,7 @@ class SpeechGenerator(generator.Generator):
result = []
priorObj = args.get('priorObj', None)
requireText = args.get('requireText', True)
- commonAncestor = self._script.findCommonAncestor(priorObj, obj)
+ commonAncestor = self._script.utilities.commonAncestor(priorObj, obj)
if obj != commonAncestor:
parent = obj.parent
if parent \
@@ -1140,13 +1142,13 @@ class SpeechGenerator(generator.Generator):
while parent and (parent.parent != parent):
if parent == commonAncestor:
break
- if not self._script.isLayoutOnly(parent):
- text = self._script.getDisplayedLabel(parent)
+ if not self._script.utilities.isLayoutOnly(parent):
+ text = self._script.utilities.displayedLabel(parent)
if not text \
and (not requireText \
or (requireText \
and 'Text' in pyatspi.listInterfaces(parent))):
- text = self._script.getDisplayedText(parent)
+ text = self._script.utilities.displayedText(parent)
if text and len(text.strip()):
# Push announcement of cell to the end
#
@@ -1197,9 +1199,8 @@ class SpeechGenerator(generator.Generator):
which contains obj.
"""
result = []
- ancestor = self._script.getAncestor(obj,
- [pyatspi.ROLE_TOOL_BAR],
- [pyatspi.ROLE_FRAME])
+ ancestor = self._script.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_TOOL_BAR], [pyatspi.ROLE_FRAME])
if ancestor:
result.extend(self._generateLabelAndName(ancestor))
result.extend(self._generateRoleName(ancestor))
@@ -1264,7 +1265,7 @@ class SpeechGenerator(generator.Generator):
for relation in obj.getRelationSet():
if relation.getRelationType() == \
pyatspi.RELATION_NODE_CHILD_OF:
- # getChildNodes assumes that we have an accessible table
+ # childNodes assumes that we have an accessible table
# interface to work with. If we don't, it will fail. So
# don't set the parent until verifying the interface we
# expect actually exists.
@@ -1286,7 +1287,7 @@ class SpeechGenerator(generator.Generator):
# uses the NODE_CHILD_OF relationship, we need to use it instead
# of the childCount.
#
- childNodes = self._script.getChildNodes(obj)
+ childNodes = self._script.utilities.childNodes(obj)
total = len(childNodes)
for i in range(0, total):
childName = self._generateName(childNodes[i])
@@ -1325,7 +1326,7 @@ class SpeechGenerator(generator.Generator):
This method should initially be called with a top-level window.
"""
result = []
- button = self._script.findDefaultButton(obj)
+ button = self._script.utilities.defaultButton(obj)
if button and button.getState().contains(pyatspi.STATE_SENSITIVE):
name = self._generateName(button)
if name:
@@ -1349,7 +1350,7 @@ class SpeechGenerator(generator.Generator):
This method should initially be called with a top-level window.
"""
result = []
- statusBar = self._script.findStatusBar(obj)
+ statusBar = self._script.utilities.statusBar(obj)
if statusBar:
name = self._generateName(statusBar)
if name:
@@ -1373,13 +1374,13 @@ class SpeechGenerator(generator.Generator):
any unfocused dialog boxes.
"""
result = []
- frame, dialog = self._script.findFrameAndDialog(obj)
+ frame, dialog = self._script.utilities.frameAndDialog(obj)
if frame:
result.append(self._generateLabelAndName(frame))
if dialog:
result.append(self._generateLabelAndName(dialog))
alertAndDialogCount = \
- self._script.getUnfocusedAlertAndDialogCount(obj)
+ self._script.utilities.unfocusedAlertAndDialogCount(obj)
if alertAndDialogCount > 0:
# Translators: this tells the user how many unfocused
# alert and dialog windows that this application has.
@@ -1401,7 +1402,8 @@ class SpeechGenerator(generator.Generator):
or an empty array if no accelerator can be found.
"""
result = []
- [mnemonic, shortcut, accelerator] = self._script.getKeyBinding(obj)
+ [mnemonic, shortcut, accelerator] = \
+ self._script.utilities.mnemonicShortcutAccelerator(obj)
if accelerator:
result.append(accelerator)
return result
@@ -1413,7 +1415,8 @@ class SpeechGenerator(generator.Generator):
"""
result = []
if settings.enableMnemonicSpeaking or args.get('forceMnemonic', False):
- [mnemonic, shortcut, accelerator] = self._script.getKeyBinding(obj)
+ [mnemonic, shortcut, accelerator] = \
+ self._script.utilities.mnemonicShortcutAccelerator(obj)
if mnemonic:
mnemonic = mnemonic[-1] # we just want a single character
if not mnemonic and shortcut:
@@ -1445,7 +1448,7 @@ class SpeechGenerator(generator.Generator):
forceTutorial))
if args.get('role', obj.getRole()) == pyatspi.ROLE_ICON \
and args.get('formatType', 'unfocused') == 'basicWhereAmI':
- frame, dialog = self._script.findFrameAndDialog(obj)
+ frame, dialog = self._script.utilities.frameAndDialog(obj)
if frame:
result.extend(self._script.tutorialGenerator.getTutorial(
frame,
diff --git a/src/orca/speechserver.py b/src/orca/speechserver.py
index 8677e54..085d36c 100644
--- a/src/orca/speechserver.py
+++ b/src/orca/speechserver.py
@@ -203,8 +203,8 @@ class SpeechServer(object):
#
event_string = keynames.getKeyName(event_string)
if orca_state.activeScript and orca_state.usePronunciationDictionary:
- event_string = \
- orca_state.activeScript.adjustForPronunciation(event_string)
+ event_string = orca_state.activeScript.\
+ utilities.adjustForPronunciation(event_string)
if eventType == orca.KeyEventType.LOCKING_LOCKED:
# Translators: this represents the state of a locking modifier
diff --git a/src/orca/structural_navigation.py b/src/orca/structural_navigation.py
index 923b37f..6a94175 100644
--- a/src/orca/structural_navigation.py
+++ b/src/orca/structural_navigation.py
@@ -741,7 +741,7 @@ class StructuralNavigation:
#
speech.speak(_("Bottom of column."))
desiredRow = iTable.nRows - 1
- elif self._script.isSameObject(thisCell, cell) \
+ elif self._script.utilities.isSameObject(thisCell, cell) \
or settings.skipBlankCells and self._isBlankCell(cell):
if colDiff < 0:
desiredCol -= 1
@@ -821,7 +821,8 @@ class StructuralNavigation:
# result in our starting at the top when looking for the next
# object rather than the current caret offset. See bug 567984.
#
- if isNext and self._script.isSameObject(obj, document):
+ if isNext \
+ and self._script.utilities.isSameObject(obj, document):
criteria = None
if criteria:
@@ -931,7 +932,7 @@ class StructuralNavigation:
# danger of skipping over the objects in between our present
# location and top of the document.
#
- if self._script.isSameObject(currentObj, document):
+ if self._script.utilities.isSameObject(currentObj, document):
currentObj = self._findNextObject(currentObj, document)
ancestors = []
@@ -1072,7 +1073,7 @@ class StructuralNavigation:
# danger of skipping over the objects in between our present
# location and top of the document.
#
- if self._script.isSameObject(currentObj, document):
+ if self._script.utilities.isSameObject(currentObj, document):
currentObj = self._findNextObject(currentObj, document)
ancestors = []
@@ -1094,7 +1095,8 @@ class StructuralNavigation:
and currentObj.parent.getRole() == obj.getRole() \
and obj.getRole() in nestableRoles)
if (not obj in ancestors or isNested) and pred(obj):
- if wrapped and self._script.isSameObject(currentObj, obj):
+ if wrapped \
+ and self._script.utilities.isSameObject(currentObj, obj):
break
else:
match = obj
@@ -1138,7 +1140,8 @@ class StructuralNavigation:
while obj and not match:
if (not obj in ancestors) and pred(obj, arg):
- if wrapped and self._script.isSameObject(currentObj, obj):
+ if wrapped \
+ and self._script.utilities.isSameObject(currentObj, obj):
break
else:
match = obj
@@ -1172,7 +1175,7 @@ class StructuralNavigation:
prevObj = obj.parent[index]
if prevObj.childCount:
prevObj = prevObj[prevObj.childCount - 1]
- elif not self._script.isSameObject(obj.parent, stopAncestor):
+ elif not self._script.utilities.isSameObject(obj.parent, stopAncestor):
prevObj = obj.parent
return prevObj
@@ -1201,7 +1204,8 @@ class StructuralNavigation:
index = obj.getIndexInParent() + 1
if 0 < index < obj.parent.childCount:
nextObj = obj.parent[index]
- elif not self._script.isSameObject(obj.parent, stopAncestor):
+ elif not self._script.utilities.isSameObject(
+ obj.parent, stopAncestor):
obj = obj.parent
else:
break
@@ -1272,7 +1276,7 @@ class StructuralNavigation:
document = self._getDocument()
while obj and obj.parent:
- if self._script.isSameObject(obj.parent, document):
+ if self._script.utilities.isSameObject(obj.parent, document):
return True
else:
obj = obj.parent
@@ -1310,7 +1314,7 @@ class StructuralNavigation:
except:
return None
else:
- return self._script.getDisplayedText(caption)
+ return self._script.utilities.displayedText(caption)
def _getTableDescription(self, obj):
"""Returns a string which describes the table."""
@@ -1372,9 +1376,8 @@ class StructuralNavigation:
if obj and obj.getRole() != pyatspi.ROLE_TABLE_CELL:
document = self._getDocument()
- obj = self._script.getAncestor(obj,
- [pyatspi.ROLE_TABLE_CELL],
- [document.getRole()])
+ obj = self._script.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_TABLE_CELL], [document.getRole()])
return obj
def getTableForCell(self, obj):
@@ -1386,9 +1389,8 @@ class StructuralNavigation:
if obj and obj.getRole() != pyatspi.ROLE_TABLE:
document = self._getDocument()
- obj = self._script.getAncestor(obj,
- [pyatspi.ROLE_TABLE],
- [document.getRole()])
+ obj = self._script.utilities.ancestorWithRole(
+ obj, [pyatspi.ROLE_TABLE], [document.getRole()])
return obj
def _isBlankCell(self, obj):
@@ -1398,12 +1400,12 @@ class StructuralNavigation:
- obj: the accessible table cell to examime
"""
- text = self._script.getDisplayedText(obj)
+ text = self._script.utilities.displayedText(obj)
if text and len(text.strip()) and text != obj.name:
return False
else:
for child in obj:
- text = self._script.getDisplayedText(child)
+ text = self._script.utilities.displayedText(child)
if text and len(text.strip()) \
or child.getRole() == pyatspi.ROLE_LINK:
return False
@@ -1419,11 +1421,11 @@ class StructuralNavigation:
text = ""
if obj and not obj.childCount:
- text = self._script.getDisplayedText(obj)
+ text = self._script.utilities.displayedText(obj)
else:
for child in obj:
- childText = self._script.getDisplayedText(child)
- text = self._script.appendString(text, childText)
+ childText = self._script.utilities.displayedText(child)
+ text = self._script.utilities.appendString(text, childText)
return text
@@ -1525,10 +1527,10 @@ class StructuralNavigation:
#
lastRow, lastCol = self.lastTableCell
lastKnownCell = table.getAccessibleAt(lastRow, lastCol)
- if self._script.isSameObject(lastKnownCell, obj):
+ if self._script.utilities.isSameObject(lastKnownCell, obj):
return [lastRow, lastCol]
else:
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
thisRow = table.getRowAtIndex(index)
thisCol = table.getColumnAtIndex(index)
return [thisRow, thisCol]
@@ -1639,7 +1641,7 @@ class StructuralNavigation:
if obj and obj.getRole() == pyatspi.ROLE_TABLE_CELL:
table = obj.parent.queryTable()
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
row = table.getRowAtIndex(index)
for col in xrange(table.nColumns):
cell = table.getAccessibleAt(row, col)
@@ -1658,7 +1660,7 @@ class StructuralNavigation:
if obj and obj.getRole() == pyatspi.ROLE_TABLE_CELL:
table = obj.parent.queryTable()
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
col = table.getColumnAtIndex(index)
for row in xrange(table.nRows):
cell = table.getAccessibleAt(row, col)
diff --git a/src/orca/tutorialgenerator.py b/src/orca/tutorialgenerator.py
index a5e31f9..e5bf334 100644
--- a/src/orca/tutorialgenerator.py
+++ b/src/orca/tutorialgenerator.py
@@ -207,7 +207,7 @@ class TutorialGenerator:
"""
utterances = []
- name = self._script.getDisplayedText(obj)
+ name = self._script.utilities.displayedText(obj)
if not name and obj.description:
name = obj.description
@@ -218,7 +218,7 @@ class TutorialGenerator:
# If this application has more than one unfocused alert or
# dialog window, tell user how to give them focus.
alertAndDialogCount = \
- self._script.getUnfocusedAlertAndDialogCount(obj)
+ self._script.utilities.unfocusedAlertAndDialogCount(obj)
if alertAndDialogCount > 0:
utterances.append(childWindowsMsg)
@@ -268,7 +268,7 @@ class TutorialGenerator:
"""
utterances = []
- name = self._script.getDisplayedText(obj)
+ name = self._script.utilities.displayedText(obj)
if not name and obj.description:
name = obj.description
@@ -385,7 +385,7 @@ class TutorialGenerator:
msg = _("Type in text.")
if (not alreadyFocused or forceTutorial) and \
- not self._script.isReadOnlyTextArea(obj):
+ not self._script.utilities.isReadOnlyTextArea(obj):
utterances.append(msg)
self._debugGenerator("_getTutorialForText",
@@ -603,9 +603,9 @@ class TutorialGenerator:
except NotImplementedError:
parent_table = None
if settings.readTableCellRow and parent_table \
- and (not self._script.isLayoutOnly(obj.parent)):
+ and not self._script.utilities.isLayoutOnly(obj.parent):
parent = obj.parent
- index = self._script.getCellIndex(obj)
+ index = self._script.utilities.cellIndex(obj)
row = parent_table.getRowAtIndex(index)
column = parent_table.getColumnAtIndex(index)
diff --git a/src/orca/where_am_I.py b/src/orca/where_am_I.py
index 66b2b43..cf25e2f 100644
--- a/src/orca/where_am_I.py
+++ b/src/orca/where_am_I.py
@@ -48,11 +48,11 @@ class WhereAmI:
list items and table cells.
"""
# [[[TODO: WDW - we purposely omit ROLE_ENTRY here because
- # there is a bug in getRealActiveDescendant: it doesn't dive
+ # there is a bug in realActiveDescendant: it doesn't dive
# deep enough into the hierarchy (see comment #12 of bug
# #542714). So, we won't treat entries inside cells as cells
# until we're more comfortable with mucking around with
- # getRealActiveDescendant.]]]
+ # realActiveDescendant.]]]
#
role = obj.getRole()
if role in [pyatspi.ROLE_TEXT,
@@ -62,11 +62,12 @@ class WhereAmI:
pyatspi.ROLE_SECTION,
pyatspi.ROLE_HEADING,
pyatspi.ROLE_DOCUMENT_FRAME]:
- ancestor = self._script.getAncestor(obj,
- [pyatspi.ROLE_TABLE_CELL,
- pyatspi.ROLE_LIST_ITEM],
- [pyatspi.ROLE_FRAME])
- if ancestor and not self._script.isLayoutOnly(ancestor.parent):
+ ancestor = self._script.utilities.ancestorWithRole(
+ obj,
+ [pyatspi.ROLE_TABLE_CELL, pyatspi.ROLE_LIST_ITEM],
+ [pyatspi.ROLE_FRAME])
+ if ancestor \
+ and not self._script.utilities.isLayoutOnly(ancestor.parent):
obj = ancestor
return obj
diff --git a/test/harness/orca-customizations.py.in b/test/harness/orca-customizations.py.in
index 184323d..ffa2d9a 100644
--- a/test/harness/orca-customizations.py.in
+++ b/test/harness/orca-customizations.py.in
@@ -19,3 +19,4 @@ orca.settings.commFailureAttemptLimit = 0
import orca.scripts.toolkits.Gecko
orca.scripts.toolkits.Gecko.sayAllOnLoad = False
+orca.scripts.toolkits.Gecko.script_settings.grabFocusOnAncestor = True
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]