[orca] Fix for bgo#618165 - Create a Utilities class for scripts; Fix for bgo#618166 - Orca's method names



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]