[orca] Rework much of Orca's LibreOffice support and prepare for focus: deprecation



commit 6478f0076f347028623d15641a3a5d4e9925d820
Author: Joanmarie Diggs <jdiggs igalia com>
Date:   Mon Nov 4 12:01:29 2013 -0500

    Rework much of Orca's LibreOffice support and prepare for focus: deprecation

 src/orca/scripts/apps/soffice/braille_generator.py |  107 +--
 src/orca/scripts/apps/soffice/script.py            | 1229 +++-----------------
 src/orca/scripts/apps/soffice/script_utilities.py  |  231 ++++-
 src/orca/scripts/apps/soffice/speech_generator.py  |  354 +++----
 src/orca/scripts/default.py                        |    6 +-
 5 files changed, 518 insertions(+), 1409 deletions(-)
---
diff --git a/src/orca/scripts/apps/soffice/braille_generator.py 
b/src/orca/scripts/apps/soffice/braille_generator.py
index bc281ab..7622493 100644
--- a/src/orca/scripts/apps/soffice/braille_generator.py
+++ b/src/orca/scripts/apps/soffice/braille_generator.py
@@ -54,32 +54,18 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
         array is returned. Overridden here so that we can get the
         dynamic row header(s).
         """
-        result = []
-        try:
-            table = obj.parent.queryTable()
-        except:
-            pass
-        else:
-            index = self._script.utilities.cellIndex(obj)
-            rowIndex = table.getRowAtIndex(index)
-            if rowIndex >= 0 \
-               and hash(obj.parent) in self._script.dynamicRowHeaders:
-                column = self._script.dynamicRowHeaders[hash(obj.parent)]
-                header = self._script.getDynamicColumnHeaderCell(obj, column)
-                try:
-                    headerText = header.queryText()
-                except:
-                    headerText = None
-                if header.childCount > 0:
-                    for child in header:
-                        text = self._script.utilities.substring(child, 0, -1)
-                        if text:
-                            result.append(text)
-                elif headerText:
-                    text = self._script.utilities.substring(header, 0, -1)
-                    if text:
-                        result.append(text)
-        return result
+
+        newOnly = args.get('newOnly', False)
+        rowHeader, columnHeader = \
+            self._script.utilities.getDynamicHeadersForCell(obj, newOnly)
+        if not rowHeader:
+            return []
+
+        text = self._script.utilities.displayedText(rowHeader)
+        if text:
+            return [text]
+
+        return []
 
     def _generateColumnHeader(self, obj, **args):
         """Returns an array of strings that represent the column header for an
@@ -87,54 +73,27 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
         array is returned. Overridden here so that we can get the
         dynamic column header(s).
         """
-        result = []
-        try:
-            table = obj.parent.queryTable()
-        except:
-            pass
-        else:
-            index = self._script.utilities.cellIndex(obj)
-            columnIndex = table.getColumnAtIndex(index)
-            if columnIndex >= 0 \
-               and hash(obj.parent) in self._script.dynamicColumnHeaders:
-                row = self._script.dynamicColumnHeaders[hash(obj.parent)]
-                header = self._script.getDynamicRowHeaderCell(obj, row)
-                try:
-                    headerText = header.queryText()
-                except:
-                    headerText = None
-                if header.childCount > 0:
-                    for child in header:
-                        text = self._script.utilities.substring(child, 0, -1)
-                        if text:
-                            result.append(text)
-                elif headerText:
-                    text = self._script.utilities.substring(header, 0, -1)
-                    if text:
-                        result.append(text)
-        return result
+
+        newOnly = args.get('newOnly', False)
+        rowHeader, columnHeader = \
+            self._script.utilities.getDynamicHeadersForCell(obj, newOnly)
+        if not columnHeader:
+            return []
+
+        text = self._script.utilities.displayedText(columnHeader)
+        if text:
+            return [text]
+
+        return []
 
     def _generateSpreadSheetCell(self, obj, **args):
-        result = []
-        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, self._script.utilities.displayedText
-        # will have already done this for us.
-        #
         try:
-            if obj.queryText():
-                objectText = self._script.utilities.substring(obj, 0, -1)
-                if objectText and len(objectText) != 0:
-                    result.append(braille.Component(
-                        obj, objectText + " " + obj.name))
-                else:
-                    result.append(braille.Component(obj, obj.name))
+            objectText = self._script.utilities.substring(obj, 0, -1)
+            cellName = self._script.utilities.spreadSheetCellName(obj)
         except:
-            pass
-        return result
+            return []
+
+        return [braille.Component(obj, " ".join((objectText, cellName)))]
 
     def _generateRealTableCell(self, obj, **args):
         """Get the speech for a table cell. If this isn't inside a
@@ -147,7 +106,7 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
         Returns a list of utterances to be spoken for the object.
         """
         result = []
-        if self._script.isSpreadSheetCell(obj):
+        if self._script.utilities.isSpreadSheetCell(obj):
             result.extend(self._generateSpreadSheetCell(obj, **args))
         else:
             # Check to see how many children this table cell has. If it's
@@ -182,7 +141,7 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
         Returns a list of utterances to be spoken for the object.
         """
         result = []
-        if self._script.isSpreadSheetCell(obj):
+        if self._script.utilities.isSpreadSheetCell(obj):
             # Adding in a check here to make sure that the parent is a
             # valid table. It's possible that the parent could be a
             # table cell too (see bug #351501).
@@ -209,8 +168,8 @@ class BrailleGenerator(braille_generator.BrailleGenerator):
                                 and pointOfReference["lastColumn"] == column))
                 if presentAll:
                     [startIndex, endIndex] = \
-                        self._script.getSpreadSheetRowRange(obj)
-                    for i in range(startIndex, endIndex+1):
+                        self._script.utilities.getTableRowRange(obj)
+                    for i in range(startIndex, endIndex):
                         cell = parentTable.getAccessibleAt(row, i)
                         showing = cell.getState().contains( \
                                       pyatspi.STATE_SHOWING)
diff --git a/src/orca/scripts/apps/soffice/script.py b/src/orca/scripts/apps/soffice/script.py
index defea95..a765bb5 100644
--- a/src/orca/scripts/apps/soffice/script.py
+++ b/src/orca/scripts/apps/soffice/script.py
@@ -1,6 +1,7 @@
 # Orca
 #
 # Copyright 2005-2009 Sun Microsystems Inc.
+# Copyright 2010-2013 The Orca Team.
 #
 # This library is free software; you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public
@@ -17,24 +18,13 @@
 # Free Software Foundation, Inc., Franklin Street, Fifth Floor,
 # Boston MA  02110-1301 USA.
 
-# [[[TODO: JD - Pylint is giving us a number of errors along these
-# lines throughout this file:
-#
-# E1103:690:Script.presentTableInfo: Instance of 'list' has no
-# 'queryTable' member (but some types could not be inferred)
-#
-# In each case, we're not querying the table interface (or asking
-# for the name) of a list, but rather of an accessible. Pylint is
-# correct about it's suggestion that it cannot infer types.]]]
-#
-# pylint: disable-msg=E1103
-
-"""Custom script for StarOffice and OpenOffice."""
+"""Custom script for LibreOffice."""
 
 __id__        = "$Id$"
 __version__   = "$Revision$"
 __date__      = "$Date$"
-__copyright__ = "Copyright (c) 2005-2009 Sun Microsystems Inc."
+__copyright__ = "Copyright (c) 2005-2009 Sun Microsystems Inc." \
+                "Copyright (c) 2010-2013 The Orca Team."
 __license__   = "LGPL"
 
 from gi.repository import Gtk
@@ -73,8 +63,6 @@ class Script(default.Script):
 
         default.Script.__init__(self, app)
 
-        # Initialize variable to None to make pylint happy.
-        #
         self.savedEnabledBrailledTextAttributes = None
         self.savedEnabledSpokenTextAttributes = None
         self.speakSpreadsheetCoordinatesCheckButton = None
@@ -84,20 +72,11 @@ class Script(default.Script):
         self.speakCellHeadersCheckButton = None
         self.speakCellSpanCheckButton = None
 
-        # Set the debug level for all the methods in this script.
-        #
-        self.debugLevel = debug.LEVEL_FINEST
-
-        # A handle to the last Writer table or Calc spread sheet cell
-        # encountered and its caret offset.
-        #
-        self.lastCell = [None, -1]
-
         # The spreadsheet input line.
         #
         self.inputLineForCell = None
 
-        # Dictionaries for the calc dynamic row and column headers.
+        # Dictionaries for the calc and writer dynamic row and column headers.
         #
         self.dynamicColumnHeaders = {}
         self.dynamicRowHeaders = {}
@@ -111,10 +90,6 @@ class Script(default.Script):
         self.lastStartOff = -1
         self.lastEndOff = -1
 
-        # Used to determine whether the caret has moved to a new paragraph.
-        #
-        self.currentParagraph = None
-
     def activate(self):
         """Called when this script is activated."""
         self.savedreadTableCellRow = \
@@ -127,7 +102,7 @@ class Script(default.Script):
         self.savedEnabledSpokenTextAttributes = \
             _settingsManager.getSetting('enabledSpokenTextAttributes')
 
-        # Account for the differences in how OOo expresses indent, 
+        # Account for the differences in how OOo expresses indent,
         # strikethrough, and margins.
         #
         attributes = _settingsManager.getSetting('allTextAttributes')
@@ -337,7 +312,7 @@ class Script(default.Script):
             Gtk.CheckButton.new_with_mnemonic(label)
         self.speakCellHeadersCheckButton.set_active(value)
         tableGrid.attach(self.speakCellHeadersCheckButton, 0, 2, 1, 1)
-           
+
         label = guilabels.TABLE_SKIP_BLANK_CELLS
         value = _settingsManager.getSetting('skipBlankCells')
         self.skipBlankCellsCheckButton = \
@@ -367,7 +342,7 @@ class Script(default.Script):
                         (prefix, script_settings.speakSpreadsheetCoordinates))
 
         value = self.speakCellCoordinatesCheckButton.get_active()
-        _settingsManager.setSetting('speakCellCoordinates', value)            
+        _settingsManager.setSetting('speakCellCoordinates', value)
         prefs.writelines("orca.settings.speakCellCoordinates = %s\n" % value)
 
         value = self.speakCellSpanCheckButton.get_active()
@@ -425,301 +400,6 @@ class Script(default.Script):
 
         return False
 
-    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,
-        return the parent table cell otherwise return the current object.
-
-        Arguments:
-        - obj: the accessible object to check.
-
-        Returns parent table cell (if in a Writer table ) or the current
-        object.
-        """
-
-        if obj.getRole() == pyatspi.ROLE_PARAGRAPH and \
-           obj.parent.getRole() == pyatspi.ROLE_TABLE_CELL:
-            return obj.parent
-        else:
-            return obj
-
-    def getTable(self, obj):
-        """Get the table that this table cell is in.
-
-        Arguments:
-        - obj: the table cell.
-
-        Return the table that this table cell is in, or None if this object
-        isn't in a table.
-        """
-
-        obj = self.adjustForWriterTable(obj)
-        if obj.getRole() == pyatspi.ROLE_TABLE_CELL and obj.parent:
-            try:
-                obj.parent.queryTable()
-            except NotImplementedError:
-                return None
-
-        return obj.parent
-
-    def getDynamicColumnHeaderCell(self, obj, column):
-        """Given a table cell, return the dynamic column header cell
-        associated with it.
-
-        Arguments:
-        - obj: the table cell.
-        - column: the column that this dynamic header is on.
-
-        Return the dynamic column header cell associated with the given
-        table cell.
-        """
-
-        obj = self.adjustForWriterTable(obj)
-        accCell = None
-        parent = obj.parent
-        try:
-            parentTable = parent.queryTable()
-        except NotImplementedError:
-            parentTable = None
-
-        if parent and parentTable:
-            index = self.utilities.cellIndex(obj)
-            row = parentTable.getRowAtIndex(index)
-            accCell = parentTable.getAccessibleAt(row, column)
-
-        return accCell
-
-    def getDynamicRowHeaderCell(self, obj, row):
-        """Given a table cell, return the dynamic row header cell
-        associated with it.
-
-        Arguments:
-        - obj: the table cell.
-        - row: the row that this dynamic header is on.
-
-        Return the dynamic row header cell associated with the given
-        table cell.
-        """
-
-        obj = self.adjustForWriterTable(obj)
-        accCell = None
-        parent = obj.parent
-        try:
-            parentTable = parent.queryTable()
-        except NotImplementedError:
-            parentTable = None
-
-        if parent and parentTable:
-            index = self.utilities.cellIndex(obj)
-            column = parentTable.getColumnAtIndex(index)
-            accCell = parentTable.getAccessibleAt(row, column)
-
-        return accCell
-
-    def locateInputLine(self, obj):
-        """Return the spread sheet input line. This only needs to be found
-        the very first time a spread sheet table cell gets focus. We use the
-        table cell to work back up the component hierarchy until we have found
-        the common panel that both it and the input line reside in. We then
-        use that as the base component to search for a component which has a
-        paragraph role. This will be the input line.
-
-        Arguments:
-        - obj: the spread sheet table cell that has just got focus.
-
-        Returns the spread sheet input line component.
-        """
-
-        inputLine = None
-        panel = obj.parent.parent.parent.parent
-        if panel and panel.getRole() == pyatspi.ROLE_PANEL:
-            allParagraphs = self.utilities.descendantsWithRole(
-                panel, pyatspi.ROLE_PARAGRAPH)
-            if len(allParagraphs) == 1:
-                inputLine = allParagraphs[0]
-            else:
-                debug.println(debug.LEVEL_SEVERE,
-                    "StarOffice: locateInputLine: incorrect paragraph count.")
-        else:
-            debug.println(debug.LEVEL_SEVERE,
-                  "StarOffice: locateInputLine: couldn't find common panel.")
-
-        return inputLine
-
-    def getSpreadSheetRowRange(self, obj):
-        """If this is spread sheet cell, return the start and end indices
-        of the spread sheet cells for the table that obj is in. Otherwise
-        return the complete range (0, parentTable.nColumns).
-
-        Arguments:
-        - obj: a spread sheet table cell.
-
-        Returns the start and end table cell indices.
-        """
-
-        parent = obj.parent
-        try:
-            parentTable = parent.queryTable()
-        except NotImplementedError:
-            parentTable = None
-
-        startIndex = 0
-        endIndex = parentTable.nColumns
-
-        if self.isSpreadSheetCell(obj):
-            extents = parent.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
-            y = extents.y
-            leftX = extents.x + 1
-            leftCell = \
-                parent.queryComponent().getAccessibleAtPoint(leftX, y, 0)
-            if leftCell:
-                table = leftCell.parent.queryTable()
-                index = self.utilities.cellIndex(leftCell)
-                startIndex = table.getColumnAtIndex(index)
-
-            rightX = extents.x + extents.width - 1
-            rightCell = \
-                parent.queryComponent().getAccessibleAtPoint(rightX, y, 0)
-            if rightCell:
-                table = rightCell.parent.queryTable()
-                index = self.utilities.cellIndex(rightCell)
-                endIndex = table.getColumnAtIndex(index)
-
-        return [startIndex, endIndex]
-
-    def isSpreadSheetCell(self, obj, startFromTable=False):
-        """Return an indication of whether the given obj is a spread sheet
-        table cell.
-
-        Arguments:
-        - obj: the object to check.
-        - startFromTable: if True, then the component hierarchy check should
-                          start from a table (as opposed to a table cell).
-
-        Returns True if this is a table cell, False otherwise.
-        """
-
-        cell = obj
-        if not startFromTable:
-            obj = obj.parent
-
-        try:
-            table = obj.queryTable()
-        except:
-            # There really doesn't seem to be a good way to identify
-            # when the user is editing a cell because it has a role
-            # of paragraph and no table in the ancestry. This hack is
-            # a carry-over from the whereAmI code.
-            #
-            if cell.getRole() == pyatspi.ROLE_PARAGRAPH:
-                top = self.utilities.topLevelObject(cell)
-                return (top and top.name.endswith(" Calc"))
-            else:
-                return False
-        else:
-            return table.nRows in [65536, 1048576]
-
-    def presentTableInfo(self, oldFocus, newFocus):
-        """Presents information relevant to a table that was just entered
-        (primarily) or exited.
-
-        Arguments:
-        - oldFocus: the first accessible to check (usually the previous
-          locusOfFocus)
-        - newFocus: the second accessible to check (usually the current
-          locusOfFocus)
-
-        Returns True if table info was presented.
-        """
-
-        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
-            # is not in a document either.
-            #
-            return False
-        elif self.isSpreadSheetCell(oldAncestor, True) \
-             or self.isSpreadSheetCell(newAncestor, True):
-            # One or both objects is a table in a spreadsheet; we just want
-            # to handle tables in documents (definitely Writer; maybe also
-            # Impress).
-            #
-            return False
-
-        try:
-            oldTable = oldAncestor.queryTable()
-        except:
-            oldTable = None
-
-        try:
-            newTable = newAncestor.queryTable()
-        except:
-            newTable = None
-
-        if oldTable == newTable == None:
-            # We're in a document, but apparently have not entered or left
-            # a table.
-            #
-            return False
-
-        if not self.utilities.isSameObject(oldAncestor, newAncestor):
-            if oldTable:
-                self.presentMessage(messages.TABLE_LEAVING)
-            if newTable:
-                self.presentMessage(
-                    messages.tableSize(newTable.nRows, newTable.nColumns))
-
-        if not newTable:
-            self.lastCell = [None, -1]
-            return True
-
-        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
-            # there are multiple paragraphs in this cell and a focus:
-            # and/or object:state-changed:focused event was emitted.
-            # If we haven't changed cells, we'll just treat this as any
-            # other paragraph and let the caret-moved events do their
-            # thing.
-            #
-            return (cell != None)
-
-        self.updateBraille(cell)
-        speech.speak(self.speechGenerator.generateSpeech(cell))
-
-        if not _settingsManager.getSetting('readTableCellRow'):
-            self.speakCellName(cell.name)
-
-        try:
-            text = newFocus.queryText()
-        except:
-            offset = -1
-        else:
-            offset = text.caretOffset
-
-        self.lastCell = [cell, offset]
-        index = self.utilities.cellIndex(cell)
-        column = newTable.getColumnAtIndex(index)
-        self.pointOfReference['lastColumn'] = column
-        row = newTable.getRowAtIndex(index)
-        self.pointOfReference['lastRow'] = row
-
-        return True
-
     def panBrailleLeft(self, inputEvent=None, panAmount=0):
         """In document content, we want to use the panning keys to browse the
         entire document.
@@ -727,7 +407,7 @@ class Script(default.Script):
 
         if self.flatReviewContext \
            or not self.isBrailleBeginningShowing() \
-           or self.isSpreadSheetCell(orca_state.locusOfFocus):
+           or self.utilities.isSpreadSheetCell(orca_state.locusOfFocus):
             return default.Script.panBrailleLeft(self, inputEvent, panAmount)
 
         obj = self.utilities.findPreviousObject(orca_state.locusOfFocus)
@@ -750,7 +430,7 @@ class Script(default.Script):
 
         if self.flatReviewContext \
            or not self.isBrailleEndShowing() \
-           or self.isSpreadSheetCell(orca_state.locusOfFocus):
+           or self.utilities.isSpreadSheetCell(orca_state.locusOfFocus):
             return default.Script.panBrailleRight(self, inputEvent, panAmount)
 
         obj = self.utilities.findNextObject(orca_state.locusOfFocus)
@@ -778,74 +458,18 @@ class Script(default.Script):
         - inputEvent: if not None, the input event that caused this action.
         """
 
-        debug.println(self.debugLevel, "StarOffice.speakInputLine.")
-
-        # Check to see if the current focus is a table cell.
-        #
-        if self.isSpreadSheetCell(orca_state.locusOfFocus):
-            try:
-                if self.inputLineForCell and self.inputLineForCell.queryText():
-                    inputLine = \
-                        self.utilities.substring(self.inputLineForCell, 0, -1)
-                    if not inputLine:
-                        inputLine = messages.EMPTY
-                    debug.println(self.debugLevel,
-                        "StarOffice.speakInputLine: contents: %s" % inputLine)
-                    self.displayBrailleMessage(inputLine, \
-                      flashTime=_settingsManager.getSetting('brailleFlashTime'))
-                    speech.speak(inputLine)
-            except NotImplementedError:
-                pass
-
-    def getTableRow(self, cell):
-        """Get the row number in the table that this table cell is on.
-
-        Arguments:
-        - cell: the table cell to get the row number for.
-
-        Return the row number that this table cell is on, or None if
-        this isn't a table cell.
-        """
-
-        row = None
-        cell = self.adjustForWriterTable(cell)
-        if cell.getRole() == pyatspi.ROLE_TABLE_CELL:
-            parent = cell.parent
-            try:
-                parentTable = parent.queryTable()
-            except NotImplementedError:
-                parentTable = None
-
-            if parent and parentTable:
-                index = self.utilities.cellIndex(cell)
-                row = parentTable.getRowAtIndex(index)
-
-        return row
-
-    def getTableColumn(self, cell):
-        """Get the column number in the table that this table cell is on.
-
-        Arguments:
-        - cell: the table cell to get the column number for.
-
-        Return the column number that this table cell is on, or None if
-        this isn't a table cell.
-        """
+        if not self.utilities.isSpreadSheetCell(orca_state.locusOfFocus):
+            return
 
-        column = None
-        cell = self.adjustForWriterTable(cell)
-        if cell.getRole() == pyatspi.ROLE_TABLE_CELL:
-            parent = cell.parent
-            try:
-                parentTable = parent.queryTable()
-            except NotImplementedError:
-                parentTable = None
+        inputLine = self.utilities.locateInputLine(orca_state.locusOfFocus)
+        if not inputLine:
+            return
 
-            if parent and parentTable:
-                index = self.utilities.cellIndex(cell)
-                column = parentTable.getColumnAtIndex(index)
+        text = self.utilities.displayedText(inputLine)
+        if not text:
+            text = messages.EMPTY
 
-        return column
+        self.presentMessage(text)
 
     def setDynamicColumnHeaders(self, inputEvent):
         """Set the row for the dynamic header columns to use when speaking
@@ -859,14 +483,14 @@ class Script(default.Script):
         - inputEvent: if not None, the input event that caused this action.
         """
 
-        debug.println(self.debugLevel, "StarOffice.setDynamicColumnHeaders.")
+        cell = orca_state.locusOfFocus
+        if cell and cell.parent.getRole() == pyatspi.ROLE_TABLE_CELL:
+            cell = cell.parent
 
-        table = self.getTable(orca_state.locusOfFocus)
+        row, column, table = self.utilities.getRowColumnAndTable(cell)
         if table:
-            row = self.getTableRow(orca_state.locusOfFocus)
             self.dynamicColumnHeaders[hash(table)] = row
-            line = messages.DYNAMIC_COLUMN_HEADER_SET % (row+1)
-            self.presentMessage(line)
+            self.presentMessage(messages.DYNAMIC_COLUMN_HEADER_SET % (row+1))
 
         return True
 
@@ -877,18 +501,17 @@ class Script(default.Script):
         - inputEvent: if not None, the input event that caused this action.
         """
 
-        debug.println(self.debugLevel, "StarOffice.clearDynamicColumnHeaders.")
+        cell = orca_state.locusOfFocus
+        if cell and cell.parent.getRole() == pyatspi.ROLE_TABLE_CELL:
+            cell = cell.parent
 
-        table = self.getTable(orca_state.locusOfFocus)
-        if table:
-            row = self.getTableRow(orca_state.locusOfFocus)
-            try:
-                del self.dynamicColumnHeaders[hash(table)]
-                line = messages.DYNAMIC_COLUMN_HEADER_CLEARED
-                speech.stop()
-                self.presentMessage(line)
-            except:
-                pass
+        row, column, table = self.utilities.getRowColumnAndTable(cell)
+        try:
+            del self.dynamicColumnHeaders[hash(table)]
+            speech.stop()
+            self.presentMessage(messages.DYNAMIC_COLUMN_HEADER_CLEARED)
+        except:
+            pass
 
         return True
 
@@ -927,14 +550,15 @@ class Script(default.Script):
         - inputEvent: if not None, the input event that caused this action.
         """
 
-        debug.println(self.debugLevel, "StarOffice.setDynamicRowHeaders.")
+        cell = orca_state.locusOfFocus
+        if cell and cell.parent.getRole() == pyatspi.ROLE_TABLE_CELL:
+            cell = cell.parent
 
-        table = self.getTable(orca_state.locusOfFocus)
+        row, column, table = self.utilities.getRowColumnAndTable(cell)
         if table:
-            column = self.getTableColumn(orca_state.locusOfFocus)
             self.dynamicRowHeaders[hash(table)] = column
-            line = messages.DYNAMIC_ROW_HEADER_SET % self.columnConvert(column+1)
-            self.presentMessage(line)
+            self.presentMessage(
+                messages.DYNAMIC_ROW_HEADER_SET % self.columnConvert(column+1))
 
         return True
 
@@ -945,18 +569,17 @@ class Script(default.Script):
         - inputEvent: if not None, the input event that caused this action.
         """
 
-        debug.println(self.debugLevel, "StarOffice.clearDynamicRowHeaders.")
+        cell = orca_state.locusOfFocus
+        if cell and cell.parent.getRole() == pyatspi.ROLE_TABLE_CELL:
+            cell = cell.parent
 
-        table = self.getTable(orca_state.locusOfFocus)
-        if table:
-            column = self.getTableColumn(orca_state.locusOfFocus)
-            try:
-                del self.dynamicRowHeaders[hash(table)]
-                line = messages.DYNAMIC_ROW_HEADER_CLEARED
-                speech.stop()
-                self.presentMessage(line)
-            except:
-                pass
+        row, column, table = self.utilities.getRowColumnAndTable(cell)
+        try:
+            del self.dynamicRowHeaders[hash(table)]
+            speech.stop()
+            self.presentMessage(messages.DYNAMIC_ROW_HEADER_CLEARED)
+        except:
+            pass
 
         return True
 
@@ -1035,7 +658,7 @@ class Script(default.Script):
         # from the last time this routine was called. If they are the same
         # then we ignore it.
         #
-        debug.println(self.debugLevel, \
+        debug.println(debug.LEVEL_INFO,
             "StarOffice.readMisspeltWord: type=%s  word=%s(%d,%d)  len=%d" % \
             (event.type, badWord, startOff, endOff, textLength))
 
@@ -1061,133 +684,6 @@ class Script(default.Script):
 
         return True
 
-    def endOfLink(self, obj, word, startOffset, endOffset):
-        """Return an indication of whether the given word contains the
-           end of a hypertext link.
-
-        Arguments:
-        - obj: an Accessible object that implements the AccessibleText
-               interface
-        - word: the word to check
-        - startOffset: the start offset for this word
-        - endOffset: the end offset for this word
-
-        Returns True if this word contains the end of a hypertext link.
-        """
-
-        nLinks = obj.queryHypertext().getNLinks()
-        links = []
-        for i in range(0, nLinks):
-            links.append(obj.queryHypertext().getLink(i))
-
-        for link in links:
-            if link.endIndex > startOffset and \
-               link.endIndex <= endOffset:
-                return True
-
-        return False
-
-    def sayWriterWord(self, obj, word, startOffset, endOffset):
-        """Speaks the given word in the appropriate voice. If this word is
-        a hypertext link and it is also at the end offset for one of the
-        links, then the word "link" is also spoken.
-
-        Arguments:
-        - obj: an Accessible object that implements the AccessibleText
-               interface
-        - word: the word to speak
-        - startOffset: the start offset for this word
-        - endOffset: the end offset for this word
-        """
-
-        voices = _settingsManager.getSetting('voices')
-
-        for i in range(startOffset, endOffset):
-            if self.utilities.linkIndex(obj, i) >= 0:
-                voice = voices[settings.HYPERLINK_VOICE]
-                break
-            elif word.isupper():
-                voice = voices[settings.UPPERCASE_VOICE]
-            else:
-                voice = voices[settings.DEFAULT_VOICE]
-
-        speech.speak(word, voice)
-        if self.endOfLink(obj, word, startOffset, endOffset):
-            speech.speak(messages.LINK)
-
-    def speakSetupLabel(self, label):
-        """Speak this Setup dialog label.
-
-        Arguments:
-        - label: the Setup dialog Label.
-        """
-
-        text = self.utilities.displayedText(label)
-        if text:
-            speech.speak(text)
-
-    def handleSetupPanel(self, panel):
-        """Find all the labels in this Setup panel and speak them.
-
-        Arguments:
-        - panel: the Setup panel.
-        """
-
-        allLabels = self.utilities.descendantsWithRole(
-            panel, pyatspi.ROLE_LABEL)
-        for label in allLabels:
-            self.speakSetupLabel(label)
-
-    def _speakWriterText(self, event, textToSpeak):
-        """Called to speak the current line or paragraph of Writer text.
-
-        Arguments:
-        - event: the Event
-        - textToSpeak: the text to speak
-        """
-
-        if not textToSpeak and event and self.speakBlankLine(event.source):
-            speech.speak(messages.BLANK, None, False)
-
-        # Check to see if there are any hypertext links in this paragraph.
-        # If no, then just speak the whole line. Otherwise, split the text
-        # to speak into words and call sayWriterWord() to speak that token
-        # in the appropriate voice.
-        #
-        try:
-            hypertext = event.source.queryHypertext()
-        except NotImplementedError:
-            hypertext = None
-
-        if not hypertext or (hypertext.getNLinks() == 0):
-            result = self.speechGenerator.generateTextIndentation(
-              event.source, line=textToSpeak)
-            if result:
-                speech.speak(result[0])
-
-            speech.speak(textToSpeak, None, False)
-        else:
-            started = False
-            startOffset = 0
-            for i in range(0, len(textToSpeak)):
-                if textToSpeak[i] == ' ':
-                    if started:
-                        endOffset = i
-                        self.sayWriterWord(event.source,
-                            textToSpeak[startOffset:endOffset+1],
-                            startOffset, endOffset)
-                        startOffset = i
-                        started = False
-                else:
-                    if not started:
-                        startOffset = i
-                        started = True
-
-            if started:
-                endOffset = len(textToSpeak)
-                self.sayWriterWord(event.source,
-                    textToSpeak[startOffset:endOffset], startOffset, endOffset)
-
     def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
         """Called when the visual object with focus changes.
 
@@ -1197,10 +693,6 @@ class Script(default.Script):
         - newLocusOfFocus: Accessible that is the new locus of focus
         """
 
-        brailleGen = self.brailleGenerator
-        details = debug.getAccessibleDetails(self.debugLevel, event.source)
-        debug.printObjectEvent(self.debugLevel, event, details)
-
         # Check to see if this is this is for the find command. See
         # comment #18 of bug #354463.
         #
@@ -1210,79 +702,10 @@ class Script(default.Script):
             self.find()
             return
 
-        # We always automatically go back to focus tracking mode when
-        # the focus changes.
-        #
         if self.flatReviewContext:
             self.toggleFlatReviewMode()
 
-        # If we are inside a paragraph inside a table cell (in Writer),
-        # then speak/braille that parent table cell (see bug #382415).
-        # Also announce that a table has been entered or left.
-        #
-        if event.source.getRole() == pyatspi.ROLE_PARAGRAPH:
-            if self.presentTableInfo(oldLocusOfFocus, newLocusOfFocus):
-                return
-
-            rolesList = [pyatspi.ROLE_PARAGRAPH,
-                         [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
-                         pyatspi.ROLE_SCROLL_PANE,
-                         pyatspi.ROLE_PANEL,
-                         pyatspi.ROLE_ROOT_PANE,
-                         pyatspi.ROLE_FRAME]
-            if self.utilities.hasMatchingHierarchy(event.source, rolesList):
-                debug.println(self.debugLevel,
-                   "StarOffice.locusOfFocusChanged - Writer: text paragraph.")
-
-                result = self.getTextLineAtCaret(event.source)
-                textToSpeak = result[0]
-                self._speakWriterText(event, textToSpeak)
-                self.displayBrailleForObject(event.source)
-                return
-
-        # Check to see if we are editing a spread sheet cell. If so, just
-        # return to avoid uttering something like "Paragraph 0 paragraph".
-        #
-        rolesList = [pyatspi.ROLE_PARAGRAPH,
-                     [pyatspi.ROLE_PANEL, pyatspi.ROLE_EXTENDED],
-                     [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
-                     pyatspi.ROLE_SCROLL_PANE,
-                     pyatspi.ROLE_PANEL,
-                     pyatspi.ROLE_ROOT_PANE,
-                     pyatspi.ROLE_FRAME,
-                     pyatspi.ROLE_APPLICATION]
-        if self.utilities.hasMatchingHierarchy(event.source, rolesList):
-            debug.println(self.debugLevel, "StarOffice.locusOfFocusChanged - " \
-                          + "Calc: cell editor.")
-            return
-
-        # Check to see if this is a Calc: spread sheet cell. If it is then
-        # we don't want to speak "not selected" after giving the cell
-        # location and contents (which is what the default locusOfFocusChanged
-        # method would now do).
-        #
-        if self.isSpreadSheetCell(event.source, True):
-            if newLocusOfFocus:
-                self.updateBraille(newLocusOfFocus)
-                utterances = \
-                    self.speechGenerator.generateSpeech(newLocusOfFocus)
-                speech.speak(utterances)
-
-                # Save the current row and column information in the table
-                # cell's table, so that we can use it the next time.
-                #
-                try:
-                    table = newLocusOfFocus.parent.queryTable()
-                except:
-                    pass
-                else:
-                    index = self.utilities.cellIndex(newLocusOfFocus)
-                    column = table.getColumnAtIndex(index)
-                    self.pointOfReference['lastColumn'] = column
-                    row = table.getRowAtIndex(index)
-                    self.pointOfReference['lastRow'] = row
-                return
-
+        # TODO - JD: Sad hack that wouldn't be needed if LO were fixed.
         # If we are in the slide presentation scroll pane, also announce
         # the current page tab. See bug #538056 for more details.
         #
@@ -1292,11 +715,7 @@ class Script(default.Script):
                      pyatspi.ROLE_ROOT_PANE,
                      pyatspi.ROLE_FRAME,
                      pyatspi.ROLE_APPLICATION]
-
         if self.utilities.hasMatchingHierarchy(event.source, rolesList):
-            debug.println(self.debugLevel, "soffice.locusOfFocusChanged - " \
-                          + "Impress: scroll pane.")
-
             for child in event.source.parent:
                 if child.getRole() == pyatspi.ROLE_PAGE_TAB_LIST:
                     for tab in child:
@@ -1305,58 +724,46 @@ class Script(default.Script):
                             utterances = \
                                 self.speechGenerator.generateSpeech(tab)
                             speech.speak(utterances)
-            # Fall-thru to process the event with the default handler.
 
-        # If we are focused on a place holder element in the slide
-        # presentation scroll pane, first present the object, then
-        # try to present each of its children. See bug #538064 for
-        # more details.
-        #
-        rolesList = [[pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_LIST_ITEM],
-                     [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
-                     pyatspi.ROLE_SCROLL_PANE,
-                     pyatspi.ROLE_PANEL,
-                     pyatspi.ROLE_PANEL,
-                     pyatspi.ROLE_ROOT_PANE,
-                     pyatspi.ROLE_FRAME,
-                     pyatspi.ROLE_APPLICATION]
-        if self.utilities.hasMatchingHierarchy(event.source, rolesList):
-            default.Script.locusOfFocusChanged(self, event,
-                                    oldLocusOfFocus, newLocusOfFocus)
-            for child in event.source:
-                speech.speak(self.utilities.substring(child, 0, -1),
-                             None, False)
-            return
+        # TODO - JD: This is a hack that needs to be done better. For now it
+        # fixes the broken echo previous word on Return.
+        elif newLocusOfFocus and oldLocusOfFocus \
+           and newLocusOfFocus.getRole() == pyatspi.ROLE_PARAGRAPH \
+           and oldLocusOfFocus.getRole() == pyatspi.ROLE_PARAGRAPH \
+           and newLocusOfFocus != oldLocusOfFocus:
+            lastKey, mods = self.utilities.lastKeyAndModifiers()
+            if lastKey == "Return" and _settingsManager.getSetting('enableEchoByWord'):
+                self.echoPreviousWord(oldLocusOfFocus)
+                return
 
-        # Combo boxes in OOo typically have two children: a text object
-        # and a list. The combo box will often intially claim to have
-        # focus, but not always. The list inside of it, however, will
-        # claim focus quite regularly. In addition, the list will do
-        # this even if the text object is editable and functionally has
-        # focus, such as the File Name combo box in the Save As dialog.
-        # We need to minimize chattiness and maximize useful information.
-        #
-        if newLocusOfFocus and oldLocusOfFocus \
-           and newLocusOfFocus.getRole() == pyatspi.ROLE_LIST \
-           and newLocusOfFocus.parent.getRole() == pyatspi.ROLE_COMBO_BOX \
-           and not self.utilities.isSameObject(newLocusOfFocus.parent,
-                                     oldLocusOfFocus.parent):
-
-            # If the combo box contents cannot be edited, just present the
-            # combo box. Otherwise, present the text object. The combo
-            # box will be included as part of the speech context.
-            #
-            state = newLocusOfFocus.parent[0].getState()
-            if not state.contains(pyatspi.STATE_EDITABLE):
-                newLocusOfFocus = newLocusOfFocus.parent
-            else:
-                newLocusOfFocus = newLocusOfFocus.parent[0]
+            # TODO - JD: And this hack is another one that needs to be done better.
+            # But this will get us to speak the entire paragraph when navigation by
+            # paragraph has occurred.
+            event_string, mods = self.utilities.lastKeyAndModifiers()
+            isControlKey = mods & settings.CTRL_MODIFIER_MASK
+            isShiftKey = mods & settings.SHIFT_MODIFIER_MASK
+            if event_string in ["Up", "Down"] and isControlKey and not isShiftKey:
+                if self.utilities.displayedText(newLocusOfFocus):
+                    speech.speak(self.utilities.displayedText(newLocusOfFocus))
+                    self.updateBraille(newLocusOfFocus)
+                    try:
+                        text = newLocusOfFocus.queryText()
+                    except:
+                        pass
+                    else:
+                        self._saveLastCursorPosition(newLocusOfFocus, text.caretOffset)
+                    return
 
         # Pass the event onto the parent class to be handled in the default way.
-
         default.Script.locusOfFocusChanged(self, event,
                                            oldLocusOfFocus, newLocusOfFocus)
 
+        if self.utilities.isDocumentCell(newLocusOfFocus):
+            row, column, table = \
+                self.utilities.getRowColumnAndTable(newLocusOfFocus.parent)
+            self.pointOfReference['lastRow'] = row
+            self.pointOfReference['lastColumn'] = column
+
     def onWindowActivated(self, event):
         """Called whenever a property on an object changes.
 
@@ -1364,10 +771,6 @@ class Script(default.Script):
         - event: the Event
         """
 
-        details = debug.getAccessibleDetails(self.debugLevel, event.source)
-        debug.printObjectEvent(self.debugLevel, event, details)
-
-        # Clear our stored misspelled word history.
         self.lastTextLength = -1
         self.lastBadWord = ''
         self.lastStartOff = -1
@@ -1391,9 +794,6 @@ class Script(default.Script):
         - event: the Event
         """
 
-        details = debug.getAccessibleDetails(self.debugLevel, event.source)
-        debug.printObjectEvent(self.debugLevel, event, details)
-
         # Check to see if if we've had a property-change event for the
         # accessible name for the option pane in the spell check dialog.
         # This (hopefully) means that the user has just corrected a
@@ -1424,54 +824,6 @@ class Script(default.Script):
 
         default.Script.onNameChanged(self, event)
 
-    def onFocus(self, event):
-        """Called whenever an object gets focus.
-
-        Arguments:
-        - event: the Event
-        """
-
-        # If we've used structural navigation commands to get here, the
-        # presentation will be handled by the StructuralNavigation class.
-        # The subsequent event will result in redundant presentation.
-        #
-        if self.isStructuralNavigationCommand():
-            return
-
-        # If this is a "focus:" event for the Calc Name combo box, catch
-        # it here to reduce verbosity (see bug #364407).
-        #
-        rolesList = [pyatspi.ROLE_LIST,
-                     pyatspi.ROLE_COMBO_BOX,
-                     pyatspi.ROLE_TOOL_BAR,
-                     pyatspi.ROLE_PANEL,
-                     pyatspi.ROLE_ROOT_PANE,
-                     pyatspi.ROLE_FRAME,
-                     pyatspi.ROLE_APPLICATION]
-        if self.utilities.hasMatchingHierarchy(event.source, rolesList):
-            debug.println(self.debugLevel, "StarOffice.onFocus - " \
-                          + "Calc: Name combo box.")
-            orca.setLocusOfFocus(event, event.source)
-            return
-
-        # OOo Writer gets rather enthusiastic with focus: events for lists.
-        # See bug 546941.
-        #
-        if event.source.getRole() == pyatspi.ROLE_LIST \
-           and orca_state.locusOfFocus \
-           and self.utilities.isSameObject(
-                orca_state.locusOfFocus.parent, event.source):
-            return
-
-        # Auto-inserted bullets and numbers are presented in braille, but not
-        # spoken. So we'll speak them before sending this event off to the
-        # default script.
-        #
-        if self.utilities.isAutoTextEvent(event):
-            speech.speak(self.speechGenerator.generateSpeech(event.source))
-
-        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.
@@ -1480,81 +832,7 @@ class Script(default.Script):
         - event: the Event
         """
 
-        handleEvent = False
-        presentEvent = True
-        if not event.source.getState().contains(pyatspi.STATE_FOCUSED):
-            # Sometimes the items in the OOo Task Pane give up focus (e.g.
-            # to a context menu) and never reclaim it. In this case, we
-            # still get object:active-descendant-changed events, but the
-            # event.source lacks STATE_FOCUSED. This causes the default
-            # script to ignore the event. See bug #523416. [[[TODO - JD:
-            # If the OOo guys fix this on their end, this hack should be
-            # removed. The OOo issue can be found here:
-            # http://www.openoffice.org/issues/show_bug.cgi?id=93083]]]
-            #
-            rolesList = [pyatspi.ROLE_LIST,
-                         pyatspi.ROLE_PANEL,
-                         pyatspi.ROLE_PANEL,
-                         pyatspi.ROLE_LIST_ITEM]
-            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.utilities.ancestorWithRole(event.source,
-                                                     [pyatspi.ROLE_TOOL_BAR],
-                                                     [pyatspi.ROLE_FRAME]) \
-                 and self.utilities.ancestorWithRole(orca_state.locusOfFocus,
-                                                     [pyatspi.ROLE_TOOL_BAR],
-                                                     [pyatspi.ROLE_FRAME]):
-                handleEvent = True
-
-        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
-            # (as opposed to those which have been automatically given focus
-            # in a dialog or alert) issue an object:state-changed:focused
-            # event, then an object:active-descendant-changed event for the
-            # list inside the combo box, and finally a focus: event for the
-            # list itself. This leads to unnecessary chattiness. As these
-            # objects look and act like combo boxes, we'll let the first of
-            # the events cause the object to be presented. Quietly setting
-            # the locusOfFocus to the activeDescendant here will prevent
-            # this event's chattiness. The final focus: event for the list
-            # is already being handled by onFocus as part of bug 546941.
-            #
-            handleEvent = True
-            presentEvent = False
-
-        if orca_state.locusOfFocus \
-           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
-            # event.
-            #
-            handleEvent = True
-            presentEvent = False
-
-        if handleEvent:
-            if presentEvent:
-                speech.stop()
-            orca.setLocusOfFocus(
-                event, event.any_data, notifyScript=presentEvent)
-
-            # We'll tuck away the activeDescendant information for future
-            # reference since the AT-SPI gives us little help in finding
-            # this.
-            #
-            self.pointOfReference['activeDescendantInfo'] = \
-                [orca_state.locusOfFocus.parent,
-                 orca_state.locusOfFocus.getIndexInParent()]
+        if self.utilities.isSameObject(event.any_data, orca_state.locusOfFocus):
             return
 
         default.Script.onActiveDescendantChanged(self, event)
@@ -1566,36 +844,29 @@ class Script(default.Script):
         - event: the Event
         """
 
-        if not event.any_data or not orca_state.locusOfFocus:
-            return
-
-        if not event.type.startswith('object:children-changed:add'):
-            return
-
         try:
-            role = event.any_data.getRole()
+            anyDataRole = event.any_data.getRole()
         except:
-            role = None
-        if role == pyatspi.ROLE_TABLE:
-            if self.isSpreadSheetCell(event.any_data, True):
+            return
+
+        if anyDataRole == pyatspi.ROLE_TABLE:
+            if self.utilities.isSpreadSheetCell(event.any_data, True):
                 orca.setLocusOfFocus(event, event.any_data)
             return
 
-        if role == pyatspi.ROLE_TABLE_CELL:
+        if anyDataRole == pyatspi.ROLE_TABLE_CELL:
             activeRow = self.pointOfReference.get('lastRow', -1)
             activeCol = self.pointOfReference.get('lastColumn', -1)
             if activeRow < 0 or activeCol < 0:
                 return
 
+            eventRow, eventCol, table = \
+                self.utilities.getRowColumnAndTable(event.any_data)
             try:
-                itable = event.source.queryTable()
+                itable = table.queryTable()
             except NotImplementedError:
                 return
 
-            index = self.utilities.cellIndex(event.any_data)
-            eventRow = itable.getRowAtIndex(index)
-            eventCol = itable.getColumnAtIndex(index)
-
             if eventRow == itable.nRows - 1 and eventCol == itable.nColumns - 1:
                 fullMessage = briefMessage = ""
                 voice = self.voices.get(settings.SYSTEM_VOICE)
@@ -1607,6 +878,9 @@ class Script(default.Script):
                     briefMessage = messages.TABLE_ROW_INSERTED
                 if fullMessage:
                     self.presentMessage(fullMessage, briefMessage, voice)
+                return
+
+        default.Script.onChildrenChanged(self, event)
 
     def onStateChanged(self, event):
         """Called whenever an object's state changes.
@@ -1615,7 +889,11 @@ class Script(default.Script):
         - event: the Event
         """
 
-        if event.source.getRole() == pyatspi.ROLE_EXTENDED:
+        if self.isStructuralNavigationCommand():
+            return
+
+        role = event.source.getRole()
+        if role == pyatspi.ROLE_EXTENDED:
             if event.source.getRoleName() == 'text frame':
                 return
 
@@ -1624,14 +902,6 @@ class Script(default.Script):
             if parent.getRoleName() == 'text frame':
                 return
 
-        # If this is state change "focused" event and event.source isn't a
-        # focused object, then just return. See bug #517502 for more details.
-        #
-        if event.type.startswith("object:state-changed:focused") \
-           and (not event.source.getState().contains(pyatspi.STATE_FOCUSED) \
-                or event.detail1 == 0):
-            return
-
         # Prevent  "object:state-changed:active" events from activating
         # the find operation. See comment #18 of bug #354463.
         #
@@ -1642,11 +912,10 @@ class Script(default.Script):
         # Announce when the toolbar buttons are toggled if we just toggled
         # them; not if we navigated to some text.
         #
-        if event.type.startswith("object:state-changed:checked") and \
-           (event.source.getRole() == pyatspi.ROLE_TOGGLE_BUTTON or \
-            event.source.getRole() == pyatspi.ROLE_PUSH_BUTTON):
+        if event.type.startswith("object:state-changed:checked") \
+           and role in [pyatspi.ROLE_TOGGLE_BUTTON, pyatspi.ROLE_PUSH_BUTTON]:
             weToggledIt = False
-            if isinstance(orca_state.lastInputEvent, \
+            if isinstance(orca_state.lastInputEvent,
                           input_event.MouseButtonEvent):
                 x = orca_state.lastInputEvent.x
                 y = orca_state.lastInputEvent.y
@@ -1662,114 +931,8 @@ class Script(default.Script):
             if weToggledIt:
                 speech.speak(self.speechGenerator.generateSpeech(event.source))
 
-        # When a new paragraph receives focus, we get a caret-moved event and
-        # two focus events (the first being object:state-changed:focused).
-        # The caret-moved event will cause us to present the text at the new
-        # location, so it is safe to set the locusOfFocus silently here.
-        # However, if we just created a new paragraph by pressing Return at
-        # the end of the current paragraph, we will only get a caret-moved
-        # event for the paragraph that just gave up focus (detail1 == -1).
-        # In this case, we will keep displaying the previous line of text,
-        # so we'll do an updateBraille() just in case.
-        #
-        if event.type.startswith("object:state-changed:focused"):
-            rolesList = [pyatspi.ROLE_PARAGRAPH,
-                         [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
-                         pyatspi.ROLE_SCROLL_PANE,
-                         pyatspi.ROLE_PANEL,
-                         pyatspi.ROLE_ROOT_PANE,
-                         pyatspi.ROLE_FRAME]
-            if self.utilities.hasMatchingHierarchy(event.source, rolesList):
-                orca.setLocusOfFocus(event, event.source, notifyScript=False)
-                if event.source != self.currentParagraph:
-                    self.updateBraille(event.source)
-                return
-
-            # If we get "object:state-changed:focused" events for children of
-            # a combo-box, just set the focus to the combo box. This is needed
-            # to help reduce the verbosity of focusing on the Calc Name combo
-            # box (see bug #364407).
-            #
-            elif event.source.parent and \
-                event.source.parent.getRole() == pyatspi.ROLE_COMBO_BOX:
-                orca.setLocusOfFocus(
-                    None, event.source.parent, notifyScript=False)
-                return
-
         default.Script.onStateChanged(self, event)
 
-    def onSelectionChanged(self, event):
-        """Called when an object's selection changes.
-
-        Arguments:
-        - event: the Event
-        """
-
-        details = debug.getAccessibleDetails(self.debugLevel, event.source)
-        debug.printObjectEvent(self.debugLevel, event, details)
-
-        # If this "object:selection-changed" is for the spread sheet Name
-        # Box, then check to see if the current locus of focus is a spread
-        # sheet cell. If it is, and the contents of the input line are
-        # different from what is displayed in that cell, then speak "has
-        # formula" and append it to the braille line.
-        #
-        rolesList = [pyatspi.ROLE_LIST,
-                     pyatspi.ROLE_COMBO_BOX,
-                     pyatspi.ROLE_PANEL,
-                     pyatspi.ROLE_TOOL_BAR,
-                     pyatspi.ROLE_PANEL,
-                     pyatspi.ROLE_ROOT_PANE,
-                     pyatspi.ROLE_FRAME,
-                     pyatspi.ROLE_APPLICATION]
-        if self.utilities.hasMatchingHierarchy(event.source, rolesList) \
-           and orca_state.locusOfFocus:
-            if orca_state.locusOfFocus.getRole() == pyatspi.ROLE_TABLE_CELL:
-                cell = orca_state.locusOfFocus
-
-                # We are getting two "object:selection-changed" events
-                # for each spread sheet cell move, so in order to prevent
-                # appending "has formula" twice, we only do it if the last
-                # cell is different from this one.
-                #
-                if cell != self.lastCell[0]:
-                    self.lastCell[0] = cell
-
-                    try:
-                        if cell.queryText():
-                            cellText = self.utilities.substring(cell, 0, -1)
-                            if cellText and len(cellText):
-                                try:
-                                    if self.inputLineForCell and \
-                                       self.inputLineForCell.queryText():
-                                        inputLine = self.utilities.substring( \
-                                                 self.inputLineForCell, 0, -1)
-                                        if inputLine and (len(inputLine) > 1) \
-                                            and (inputLine[0] == "="):
-                                            hf = messages.HAS_FORMULA
-                                            speech.speak(" %s" % hf,
-                                                         None, False)
-                                            self.presentItemsInBraille([hf])
-                                            #
-                                            # Fall-thru to process the event
-                                            # with the default handler.
-                                except NotImplementedError:
-                                    pass
-                    except NotImplementedError:
-                        pass
-
-        default.Script.onSelectionChanged(self, event)
-
-    def speakCellName(self, name):
-        """Speaks the given cell name.
-
-        Arguments:
-        - name: the name of the cell
-        """
-
-        line = messages.CELL % name
-        speech.speak(line)
-
     def onCaretMoved(self, event):
         """Called whenever the caret moves.
 
@@ -1777,166 +940,33 @@ class Script(default.Script):
         - event: the Event
         """
 
-        # If we've used structural navigation commands to get here, the
-        # presentation will be handled by the StructuralNavigation class.
-        # The subsequent event will result in redundant presentation.
-        #
         if self.isStructuralNavigationCommand():
             return
 
-        if self.utilities.isDuplicateEvent(event):
-            return
-
-        # If we are losing focus and we in:
-        # 1/ a paragraph in an ooimpress slide presentation
-        # 2/ a paragraph in an oowriter text document
-        # and the last thing the user typed was a Return, and echo by word
-        # is enabled, and the last focused object was not of role "unknown",
-        # then echo the previous word that the user typed.
-        # See bug #538053 and bug #538835 for more details.
-        #
-        if event.detail1 == -1:
-            # ooimpress paragraph in a slide presentation.
-            rolesList = [pyatspi.ROLE_PARAGRAPH,
-                         [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_LIST_ITEM],
-                         [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
-                         pyatspi.ROLE_SCROLL_PANE,
-                         pyatspi.ROLE_PANEL,
-                         pyatspi.ROLE_PANEL,
-                         pyatspi.ROLE_ROOT_PANE,
-                         pyatspi.ROLE_FRAME,
-                         pyatspi.ROLE_APPLICATION]
-
-            # oowriter paragraph in a text document.
-            rolesList1 = [pyatspi.ROLE_PARAGRAPH,
-                          [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_DOCUMENT_FRAME],
-                          pyatspi.ROLE_SCROLL_PANE,
-                          pyatspi.ROLE_PANEL,
-                          pyatspi.ROLE_ROOT_PANE,
-                          pyatspi.ROLE_FRAME,
-                          pyatspi.ROLE_APPLICATION]
-            if _settingsManager.getSetting('enableEchoByWord') and \
-               (self.utilities.hasMatchingHierarchy(event.source, rolesList) or
-                self.utilities.hasMatchingHierarchy(event.source, rolesList1)):
-                keyString, mods = self.utilities.lastKeyAndModifiers()
-                focusRole = orca_state.locusOfFocus.getRole()
-                if focusRole != pyatspi.ROLE_UNKNOWN and keyString == "Return":
-                    result = self.utilities.substring(event.source, 0, -1)
-                    self.echoPreviousWord(event.source, len(result))
-                    return
-
-        # Otherwise, if the object is losing focus, then just ignore this event.
-        #
         if event.detail1 == -1:
             return
 
-        if self.lastCell[0] == event.source.parent:
-            if self.lastCell[1] == event.detail1:
-                # We took care of this in a focus event (our position has not
-                # changed within the cell)
-                #
-                return
-            else:
-                # We're in the same cell, but at a different position. Update
-                # our stored location and then let the normal caret-moved
-                # processing take place.
-                #
-                self.lastCell[1] = event.detail1
-
-        event_string, mods = self.utilities.lastKeyAndModifiers()
-        isControlKey = mods & settings.CTRL_MODIFIER_MASK
-        isShiftKey = mods & settings.SHIFT_MODIFIER_MASK
+        obj, offset = self.pointOfReference.get("lastCursorPosition", (None, -1))
+        if offset == event.detail1 \
+           and self.utilities.isSameObject(obj, event.source):
+            return
 
-        # If the last input event was a keyboard event of Control-Up or
-        # Control-Down, we want to speak the whole paragraph rather than
-        # just the current line. In addition, we need to filter out some
-        # creative uses of the caret-moved event on the part of the OOo
-        # guys.
+        # The lists and combo boxes in the Formatting toolbar emit
+        # object:active-descendant-changed events which cause us
+        # to set the locusOfFocus to the list item. If the user then
+        # arrows within the text portion, we will not present it due
+        # to the event not being from the locusOfFocus. A similar
+        # issue is present in the Target entry of the Hyperlink dialog
+        # for OOo 3.2.
         #
-        if event_string in ["Up", "Down"] and isControlKey and not isShiftKey:
-            # If we moved to the next paragraph, the event.source index should
-            # be larger than the current paragraph's index. If we moved to the
-            # previous paragraph it should be smaller. Otherwise, it's bogus.
-            #
-            eventIndex = event.source.getIndexInParent()
-            if self.currentParagraph:
-                paraIndex = self.currentParagraph.getIndexInParent()
-            else:
-                paraIndex = eventIndex
+        if event.source.getRole() == pyatspi.ROLE_TEXT \
+           and self.utilities.ancestorWithRole(
+               event.source,
+               [pyatspi.ROLE_TOOL_BAR, pyatspi.ROLE_DIALOG],
+               [pyatspi.ROLE_FRAME]):
+            orca.setLocusOfFocus(event, event.source, False)
 
-            if (event_string == "Down" and (eventIndex - paraIndex <= 0)) \
-               or (event_string == "Up" and (eventIndex - paraIndex >= 0)):
-                return
-
-            result = self.utilities.substring(event.source, 0, -1)
-            self._speakWriterText(event, result)
-            self.displayBrailleForObject(event.source)
-        else:
-            # The lists and combo boxes in the Formatting toolbar emit
-            # object:active-descendant-changed events which cause us
-            # to set the locusOfFocus to the list item. If the user then
-            # arrows within the text portion, we will not present it due
-            # to the event not being from the locusOfFocus. A similar
-            # issue is present in the Target entry of the Hyperlink dialog
-            # for OOo 3.2.
-            #
-            if event.source.getRole() == pyatspi.ROLE_TEXT \
-               and self.utilities.ancestorWithRole(
-                    event.source, 
-                    [pyatspi.ROLE_TOOL_BAR, pyatspi.ROLE_DIALOG],
-                    [pyatspi.ROLE_FRAME]):
-                orca.setLocusOfFocus(event, event.source, False)
-            default.Script.onCaretMoved(self, event)
-
-        # If we're still here, we must be convinced that this paragraph
-        # coincides with our actual location.
-        #
-        self.currentParagraph = event.source
-
-    def speakBlankLine(self, obj):
-        """Returns True if a blank line should be spoken.
-        Otherwise, returns False.
-        """
-
-        # Get the the AccessibleText interface.
-        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)
-
-        # 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 _settingsManager.getSetting('speakBlankLines')
-
-    def onTextInserted(self, event):
-        """Called whenever text is inserted into an object.  Overridden here
-        to handle the case when the inserted text was pasted via middle mouse
-        click.
-
-        Arguments:
-        - event: the Event
-        """
-
-        # Because event.source is the paragraph where the text was inserted
-        # and locusOfFocus is the selected text, the default onTextInserted
-        # will return without speaking the text that was pasted.
-        #
-        text = event.any_data
-        if isinstance(orca_state.lastInputEvent,
-                        input_event.MouseButtonEvent) and \
-             orca_state.lastInputEvent.button == "2":
-            if text.isupper():
-                speech.speak(text, self.voices[settings.UPPERCASE_VOICE])
-            else:
-                speech.speak(text)
-        else:
-            default.Script.onTextInserted(self, event)
+        default.Script.onCaretMoved(self, event)
 
     def getTextLineAtCaret(self, obj, offset=None):
         """Gets the line of text where the caret is. Overridden here to
@@ -1976,3 +1006,14 @@ class Script(default.Script):
 
         return textLine
 
+    def skipObjectEvent(self, event):
+        # NOTE: This is here temporarily as part of the preparation for the
+        # deprecation/removal of accessible "focus:" events. Once the change
+        # has been completed, this method should be removed from this script.
+        if event.type == "focus:":
+            return True
+
+        if event.type == "object:state-changed:focused":
+            return False
+
+        return default.Script.skipObjectEvent(self, event)
diff --git a/src/orca/scripts/apps/soffice/script_utilities.py 
b/src/orca/scripts/apps/soffice/script_utilities.py
index 93b5583..4aabc96 100644
--- a/src/orca/scripts/apps/soffice/script_utilities.py
+++ b/src/orca/scripts/apps/soffice/script_utilities.py
@@ -97,27 +97,174 @@ class Utilities(script_utilities.Utilities):
 
         return readOnly
 
-    def isDuplicateEvent(self, event):
-        """Returns True if we believe this event is a duplicate which we
-        wish to ignore."""
+    def isSpreadSheetCell(self, obj, startFromTable=False):
+        """Return an indication of whether the given obj is a spread sheet
+        table cell.
 
-        if not event:
+        Arguments:
+        - obj: the object to check.
+        - startFromTable: if True, then the component hierarchy check should
+          start from a table (as opposed to a table cell).
+
+        Returns True if this is a table cell, False otherwise.
+        """
+
+        cell = obj
+        if not startFromTable:
+            obj = obj.parent
+
+        try:
+            table = obj.queryTable()
+        except:
+            # There really doesn't seem to be a good way to identify
+            # when the user is editing a cell because it has a role
+            # of paragraph and no table in the ancestry. This hack is
+            # a carry-over from the whereAmI code.
+            #
+            if cell.getRole() == pyatspi.ROLE_PARAGRAPH:
+                top = self.topLevelObject(cell)
+                return (top and top.name.endswith(" Calc"))
+            else:
+                return False
+        else:
+            return table.nRows in [65536, 1048576]
+
+    def isDocumentCell(self, cell):
+        isCell = lambda x: x and x.getRole() == pyatspi.ROLE_TABLE_CELL
+        if not isCell(cell):
+            cell = pyatspi.findAncestor(cell, isCell)
+
+        if not cell or self.isSpreadSheetCell(cell):
             return False
 
-        if event.type.startswith("object:text-caret-moved"):
+        isDocument = lambda x: x and x.getRole() == pyatspi.ROLE_DOCUMENT_FRAME
+        return pyatspi.findAncestor(cell, isDocument) != None
+
+    def spreadSheetCellName(self, cell):
+        nameList = cell.name.split()
+        for name in nameList:
+            if not name.isalpha() and name.isalnum():
+                return name
+
+        return ''
+
+    def getRowColumnAndTable(self, cell):
+        """Returns the (row, column, table) tuple for cell."""
+
+        if not (cell and cell.getRole() == pyatspi.ROLE_TABLE_CELL):
+            return -1, -1, None
+
+        cellParent = cell.parent
+        if cellParent and cellParent.getRole() == pyatspi.ROLE_TABLE_CELL:
+            cell = cellParent
+            cellParent = cell.parent
+
+        table = cellParent
+        if table and table.getRole() != pyatspi.ROLE_TABLE:
+            table = table.parent
+
+        try:
+            iTable = table.queryTable()
+        except:
+            return -1, -1, None
+
+        index = self.cellIndex(cell)
+        row = iTable.getRowAtIndex(index)
+        column = iTable.getColumnAtIndex(index)
+
+        return row, column, table
+
+    def getShowingCellsInRow(self, obj):
+        row, column, parentTable = self.getRowColumnAndTable(obj)
+        try:
+            table = parentTable.queryTable()
+        except:
+            return []
+
+        startIndex, endIndex = self.getTableRowRange(obj)
+        cells = []
+        for i in range(startIndex, endIndex):
+            cell = table.getAccessibleAt(row, i)
             try:
-                obj, offset = \
-                    self._script.pointOfReference["lastCursorPosition"]
+                showing = cell.getState().contains(pyatspi.STATE_SHOWING)
             except:
-                return False
-            else:
-                # Doing an intentional equality check rather than calling
-                # isSameObject() because we'd rather double-present an
-                # object than not present it at all.
-                #
-                return obj == event.source and offset == event.detail1
+                continue
+            if showing:
+                cells.append(cell)
 
-        return False
+        return cells
+
+    def getTableRowRange(self, obj):
+        """If this is spread sheet cell, return the start and end indices
+        of the spread sheet cells for the table that obj is in. Otherwise
+        return the complete range (0, parentTable.nColumns).
+
+        Arguments:
+        - obj: a table cell.
+
+        Returns the start and end table cell indices.
+        """
+
+        parent = obj.parent
+        try:
+            parentTable = parent.queryTable()
+        except NotImplementedError:
+            parentTable = None
+
+        startIndex = 0
+        endIndex = parentTable.nColumns
+
+        if self.isSpreadSheetCell(obj):
+            extents = parent.queryComponent().getExtents(pyatspi.DESKTOP_COORDS)
+            y = extents.y
+            leftX = extents.x + 1
+            leftCell = \
+                parent.queryComponent().getAccessibleAtPoint(leftX, y, 0)
+            if leftCell:
+                table = leftCell.parent.queryTable()
+                index = self.cellIndex(leftCell)
+                startIndex = table.getColumnAtIndex(index)
+
+            rightX = extents.x + extents.width - 1
+            rightCell = \
+                parent.queryComponent().getAccessibleAtPoint(rightX, y, 0)
+            if rightCell:
+                table = rightCell.parent.queryTable()
+                index = self.cellIndex(rightCell)
+                endIndex = table.getColumnAtIndex(index) + 1
+
+        return [startIndex, endIndex]
+
+    def getDynamicHeadersForCell(self, obj, onlyIfNew=False):
+        if not (self._script.dynamicRowHeaders or self._script.dynamicColumnHeaders):
+            return None, None
+
+        objRow, objCol, table = self.getRowColumnAndTable(obj)
+        if not table:
+            return None, None
+
+        headersRow = self._script.dynamicColumnHeaders.get(hash(table))
+        headersCol = self._script.dynamicRowHeaders.get(hash(table))
+        if headersRow == objRow or headersCol == objCol:
+            return None, None
+
+        getRowHeader = headersCol != None
+        getColHeader = headersRow != None
+        if onlyIfNew:
+            getRowHeader = \
+                getRowHeader and objRow != self._script.pointOfReference.get("lastRow")
+            getColHeader = \
+                getColHeader and objCol!= self._script.pointOfReference.get("lastColumn")
+
+        parentTable = table.queryTable()
+        rowHeader, colHeader = None, None
+        if getColHeader:
+            colHeader = parentTable.getAccessibleAt(headersRow, objCol)
+
+        if getRowHeader:
+            rowHeader = parentTable.getAccessibleAt(objRow, headersCol)
+
+        return rowHeader, colHeader
 
     def isSameObject(self, obj1, obj2):
         same = script_utilities.Utilities.isSameObject(self, obj1, obj2)
@@ -134,6 +281,56 @@ class Utilities(script_utilities.Utilities):
 
         return same
 
+    def isLayoutOnly(self, obj):
+        """Returns True if the given object is a container which has
+        no presentable information (label, name, displayed text, etc.)."""
+
+        role = obj.getRole()
+        if role == pyatspi.ROLE_PANEL and obj.childCount == 1:
+            if obj.name and obj.name == obj[0].name:
+                return True
+
+        if role == pyatspi.ROLE_LIST \
+           and obj.parent.getRole() == pyatspi.ROLE_COMBO_BOX:
+            return True
+
+        return script_utilities.Utilities.isLayoutOnly(self, obj)
+
+    def locateInputLine(self, obj):
+        """Return the spread sheet input line. This only needs to be found
+        the very first time a spread sheet table cell gets focus. We use the
+        table cell to work back up the component hierarchy until we have found
+        the common panel that both it and the input line reside in. We then
+        use that as the base component to search for a component which has a
+        paragraph role. This will be the input line.
+
+        Arguments:
+        - obj: the spread sheet table cell that has just got focus.
+
+        Returns the spread sheet input line component.
+        """
+
+        if self._script.inputLineForCell:
+            return self._script.inputLineForCell
+
+        isScrollPane = lambda x: x and x.getRole() == pyatspi.ROLE_SCROLL_PANE
+        scrollPane = pyatspi.findAncestor(obj, isScrollPane)
+        if not scrollPane:
+            return None
+
+        toolbar = None
+        for child in scrollPane.parent:
+            if child and child.getRole() == pyatspi.ROLE_TOOL_BAR:
+                toolbar = child
+                break
+
+        isParagraph = lambda x: x and x.getRole() == pyatspi.ROLE_PARAGRAPH
+        allParagraphs = pyatspi.findAllDescendants(toolbar, isParagraph)
+        if len(allParagraphs) == 1:
+            self._script.inputLineForCell = allParagraphs[0]
+
+        return self._script.inputLineForCell
+
     def frameAndDialog(self, obj):
         """Returns the frame and (possibly) the dialog containing
         the object. Overridden here for presentation of the title
@@ -144,7 +341,7 @@ class Utilities(script_utilities.Utilities):
         its thing.
         """
 
-        if not self._script.isSpreadSheetCell(obj):
+        if not self.isSpreadSheetCell(obj):
             return script_utilities.Utilities.frameAndDialog(self, obj)
 
         results = [None, None]
@@ -498,7 +695,7 @@ class Utilities(script_utilities.Utilities):
         # Things only seem broken for certain tables, e.g. the Paths table.
         # TODO - JD: File the LibreOffice bugs and reference them here.
         if obj.getRole() != pyatspi.ROLE_TABLE \
-           or self._script.isSpreadSheetCell(obj, True):
+           or self.isSpreadSheetCell(obj, True):
             return script_utilities.Utilities.selectedChildren(self, obj)
 
         try:
diff --git a/src/orca/scripts/apps/soffice/speech_generator.py 
b/src/orca/scripts/apps/soffice/speech_generator.py
index a747ef7..80150e1 100644
--- a/src/orca/scripts/apps/soffice/speech_generator.py
+++ b/src/orca/scripts/apps/soffice/speech_generator.py
@@ -36,9 +36,6 @@ from . import script_settings
 _settingsManager = settings_manager.getManager()
 
 class SpeechGenerator(speech_generator.SpeechGenerator):
-
-    # pylint: disable-msg=W0142
-
     def __init__(self, script):
         speech_generator.SpeechGenerator.__init__(self, script)
 
@@ -164,7 +161,7 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
         """
 
         result = []
-        if not self._script.isSpreadSheetCell(obj, startFromTable=True):
+        if not self._script.utilities.isSpreadSheetCell(obj, startFromTable=True):
             result.extend(speech_generator.SpeechGenerator.\
                 _generateAvailability(self, obj, **args))
 
@@ -199,6 +196,23 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
             result.extend(acss)
         return result
 
+    def _generateCurrentLineText(self, obj, **args):
+        if self._script.utilities.isDocumentCell(obj.parent):
+            priorObj = args.get('priorObj', None)
+            if priorObj and priorObj.parent != obj.parent:
+                return []
+
+        # TODO - JD: The SayLine, etc. code should be generated and not put
+        # together in the scripts. In addition, the voice crap needs to go
+        # here. Then it needs to be removed from the scripts.
+        [text, caretOffset, startOffset] = self._script.getTextLineAtCaret(obj)
+        text = self._script.utilities.adjustForLinks(obj, text, startOffset)
+        text = self._script.utilities.adjustForRepeats(text)
+        if not text:
+            text = [messages.BLANK]
+
+        return [text]
+
     def _generateToggleState(self, obj, **args):
         """Treat toggle buttons in the toolbar specially. This is so we can
         have more natural sounding speech such as "bold on", "bold off", etc."""
@@ -224,71 +238,27 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
         is returned. Overridden here so that we can get the dynamic
         row header(s).
         """
+
+        if _settingsManager.getSetting('readTableCellRow'):
+            return []
+
+        newOnly = args.get('newOnly', False)
+        rowHeader, columnHeader = \
+            self._script.utilities.getDynamicHeadersForCell(obj, newOnly)
+        if not rowHeader:
+            return []
+
         result = []
-        acss = self.voice(speech_generator.DEFAULT)
-        try:
-            table = obj.parent.queryTable()
-        except:
-            pass
-        else:
-            index = self._script.utilities.cellIndex(obj)
-            rowIndex = table.getRowAtIndex(index)
-            if rowIndex >= 0 \
-               and hash(obj.parent) in self._script.dynamicRowHeaders:
-                column = self._script.dynamicRowHeaders[hash(obj.parent)]
-                header = self._script.getDynamicColumnHeaderCell(obj, column)
-                try:
-                    headerText = header.queryText()
-                except:
-                    headerText = None
-                if header.childCount > 0:
-                    for child in header:
-                        text = self._script.utilities.substring(child, 0, -1)
-                        if text:
-                            result.append(text)
-                elif headerText:
-                    text = self._script.utilities.substring(header, 0, -1)
-                    if text:
-                        result.append(text)
-        if result:
-            result.extend(acss)
+        text = self._script.utilities.displayedText(rowHeader)
+        if text:
+            result.append(text)
+            result.extend(self.voice(speech_generator.DEFAULT))
+
         return result
 
     def _generateNewRowHeader(self, obj, **args):
-        result = []
-        acss = self.voice(speech_generator.DEFAULT)
-        # Check to see if this spread sheet cell has either a dynamic
-        # row heading associated with it.
-        #
-        table = self._script.getTable(obj)
-        parent = obj.parent
-        try:
-            parentTable = parent.queryTable()
-        except:
-            parentTable = None
-        index = self._script.utilities.cellIndex(obj)
-        if "lastRow" in self._script.pointOfReference and parentTable \
-           and self._script.pointOfReference["lastRow"] != \
-           parentTable.getRowAtIndex(index):
-            if hash(parent) in self._script.dynamicRowHeaders:
-                column = self._script.dynamicRowHeaders[hash(parent)]
-                header = self._script.getDynamicColumnHeaderCell(obj, column)
-                try:
-                    headerText = header.queryText()
-                except:
-                    headerText = None
-                if header.childCount > 0:
-                    for child in header:
-                        text = self._script.utilities.substring(child, 0, -1)
-                        if text:
-                            result.append(text)
-                elif headerText:
-                    text = self._script.utilities.substring(header, 0, -1)
-                    if text:
-                        result.append(text)
-        if result:
-            result.extend(acss)
-        return result
+        args['newOnly'] = True
+        return self._generateRowHeader(obj, **args)
 
     def _generateColumnHeader(self, obj, **args):
         """Returns an array of strings (and possibly voice and audio
@@ -297,71 +267,24 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
         is returned. Overridden here so that we can get the dynamic
         column header(s).
         """
+
+        newOnly = args.get('newOnly', False)
+        rowHeader, columnHeader = \
+            self._script.utilities.getDynamicHeadersForCell(obj, newOnly)
+        if not columnHeader:
+            return []
+
         result = []
-        acss = self.voice(speech_generator.DEFAULT)
-        try:
-            table = obj.parent.queryTable()
-        except:
-            pass
-        else:
-            index = self._script.utilities.cellIndex(obj)
-            columnIndex = table.getColumnAtIndex(index)
-            if columnIndex >= 0 \
-               and hash(obj.parent) in self._script.dynamicColumnHeaders:
-                row = self._script.dynamicColumnHeaders[hash(obj.parent)]
-                header = self._script.getDynamicRowHeaderCell(obj, row)
-                try:
-                    headerText = header.queryText()
-                except:
-                    headerText = None
-                if header.childCount > 0:
-                    for child in header:
-                        text = self._script.utilities.substring(child, 0, -1)
-                        if text:
-                            result.append(text)
-                elif headerText:
-                    text = self._script.utilities.substring(header, 0, -1)
-                    if text:
-                        result.append(text)
-        if result:
-            result.extend(acss)
+        text = self._script.utilities.displayedText(columnHeader)
+        if text:
+            result.append(text)
+            result.extend(self.voice(speech_generator.DEFAULT))
+
         return result
 
     def _generateNewColumnHeader(self, obj, **args):
-        result = []
-        acss = self.voice(speech_generator.DEFAULT)
-        # Check to see if this spread sheet cell has either a dynamic
-        # row heading associated with it.
-        #
-        table = self._script.getTable(obj)
-        parent = obj.parent
-        try:
-            parentTable = parent.queryTable()
-        except:
-            parentTable = None
-        index = self._script.utilities.cellIndex(obj)
-        if parentTable and "lastColumn" in self._script.pointOfReference \
-           and self._script.pointOfReference["lastColumn"] != \
-           parentTable.getColumnAtIndex(index):
-            if hash(parent) in self._script.dynamicColumnHeaders:
-                row = self._script.dynamicColumnHeaders[hash(parent)]
-                header = self._script.getDynamicRowHeaderCell(obj, row)
-                try:
-                    headerText = header.queryText()
-                except:
-                    headerText = None
-                if header.childCount > 0:
-                    for child in header:
-                        text = self._script.utilities.substring(child, 0, -1)
-                        if text:
-                            result.append(text)
-                elif headerText:
-                    text = self._script.utilities.substring(header, 0, -1)
-                    if text:
-                        result.append(text)
-        if result:
-            result.extend(acss)
-        return result
+        args['newOnly'] = True
+        return self._generateColumnHeader(obj, **args)
 
     def _generateTooLong(self, obj, **args):
         """If there is text in this spread sheet cell, compare the size of
@@ -398,47 +321,56 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
             result.extend(acss)
         return result
 
+    def _generateHasFormula(self, obj, **args):
+        inputLine = self._script.utilities.locateInputLine(obj)
+        if not inputLine:
+            return []
+
+        text = self._script.utilities.displayedText(inputLine)
+        if text.startswith("="):
+            result = [messages.HAS_FORMULA]
+            result.extend(self.voice(speech_generator.SYSTEM))
+            return result
+
+        return []
+
     def _generateSpreadSheetCell(self, obj, **args):
         result = []
-        acss = self.voice(speech_generator.DEFAULT)
-        if self._script.inputLineForCell == None:
-            self._script.inputLineForCell = \
-                self._script.locateInputLine(obj)
+        isBasicWhereAmI = args.get('formatType') == 'basicWhereAmI'
+        speakCoordinates = script_settings.speakSpreadsheetCoordinates
+
         try:
-            if obj.queryText():
-                objectText = self._script.utilities.substring(obj, 0, -1)
-                if (not script_settings.speakSpreadsheetCoordinates \
-                    or args.get('formatType', 'unfocused') == 'basicWhereAmI') \
-                   and len(objectText) == 0:
-                    objectText = messages.BLANK
-                result.append(objectText)
-                result.extend(acss)
-        except NotImplementedError:
-            pass
+            objectText = self._script.utilities.substring(obj, 0, -1)
+        except:
+            objectText = ''
+        if not objectText and (isBasicWhereAmI or not speakCoordinates):
+            objectText = messages.BLANK
 
-        if script_settings.speakSpreadsheetCoordinates \
-           and args.get('formatType', 'unfocused') != 'basicWhereAmI':
-            nameList = obj.name.split()
-            # We were assuming that the word for "cell" would always
-            # precede the coordinates. This is not the case for all
-            # languages (e.g. Hungarian). See bug #562532. Therefore
-            # examine each item and choose the one which contains a
-            # digit.
-            #
-            for name in nameList:
-                for char in name:
-                    if char.isdigit():
-                        result.append(name)
-                        break
+        if objectText:
+            result.append(objectText)
+
+        if speakCoordinates and not isBasicWhereAmI:
+            result.append(self._script.utilities.spreadSheetCellName(obj))
 
         if result:
-            result.extend(acss)
+            result.extend(self.voice(speech_generator.DEFAULT))
+
+        if _settingsManager.getSetting('readTableCellRow'):
+            row, col, table = self._script.utilities.getRowColumnAndTable(obj)
+            lastRow = self._script.pointOfReference.get("lastRow")
+            if row != lastRow:
+                return result
 
         tooLong = self._generateTooLong(obj, **args)
-        if tooLong and len(tooLong):
+        if tooLong:
             result.extend(self._generatePause(obj, **args))
             result.extend(tooLong)
 
+        hasFormula = self._generateHasFormula(obj, **args)
+        if hasFormula:
+            result.extend(self._generatePause(obj, **args))
+            result.extend(hasFormula)
+
         return result
 
     def _generateRealTableCell(self, obj, **args):
@@ -451,77 +383,31 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
 
         Returns a list of utterances to be spoken for the object.
         """
-        result = []
-        if self._script.isSpreadSheetCell(obj):
-            result.extend(self._generateSpreadSheetCell(obj, **args))
-        else:
-            # Check to see how many children this table cell has. If it's
-            # just one (or none), then pass it on to the superclass to be
-            # processed.
-            #
-            # If it's more than one, then get the speech for each child,
-            # and call this method again.
-            #
-            if obj.childCount <= 1:
-                result.extend(speech_generator.SpeechGenerator.\
-                              _generateRealTableCell(self, obj, **args))
-            else:
-                for child in obj:
-                    result.extend(self._generateRealTableCell(child, **args))
-        return result
+        if self._script.utilities.isSpreadSheetCell(obj):
+            return self._generateSpreadSheetCell(obj, **args)
+
+        return speech_generator.SpeechGenerator._generateRealTableCell(
+            self, obj, **args)
 
     def _generateTableCellRow(self, obj, **args):
-        """Get the speech for a table cell row or a single table cell
-        if _settingsManager.getSetting('readTableCellRow') is False. If this isn't inside a
-        spread sheet, just return the utterances returned by the default
-        table cell speech handler.
+        """Get the speech for a table cell row if the user wants to hear
+        the full row and if the row has actually changed."""
 
-        Arguments:
-        - obj: the table cell
+        speakFullRow = _settingsManager.getSetting('readTableCellRow')
+        if speakFullRow:
+            row, column, table = \
+                self._script.utilities.getRowColumnAndTable(obj)
+            lastRow = self._script.pointOfReference.get("lastRow")
+            speakFullRow = row != lastRow
+
+        if not speakFullRow:
+            return self._generateRealTableCell(obj, **args)
 
-        Returns a list of utterances to be spoken for the object.
-        """
         result = []
-        if self._script.isSpreadSheetCell(obj):
-            if _settingsManager.getSetting('readTableCellRow'):
-                parent = obj.parent
-                parentTable = parent.queryTable()
-                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
-                # table cells (the user has moved focus up or down a row),
-                # or just the current one (focus has moved left or right in
-                # the same row).
-                #
-                speakAll = True
-                if "lastRow" in self._script.pointOfReference and \
-                    "lastColumn" in self._script.pointOfReference:
-                    pointOfReference = self._script.pointOfReference
-                    speakAll = (pointOfReference["lastRow"] != row) or \
-                           ((row == 0 or row == parentTable.nRows-1) and \
-                            pointOfReference["lastColumn"] == column)
-                if speakAll:
-                    [startIndex, endIndex] = \
-                        self._script.getSpreadSheetRowRange(obj)
-                    for i in range(startIndex, endIndex+1):
-                        cell = parentTable.getAccessibleAt(row, i)
-                        showing = cell.getState().contains( \
-                                      pyatspi.STATE_SHOWING)
-                        if showing:
-                            result.extend(self._generateRealTableCell(
-                                              cell, **args))
-                else:
-                    result.extend(self._generateRealTableCell(obj, **args))
-            else:
-                result.extend(self._generateRealTableCell(obj, **args))
-        else:
-            result.extend(
-                speech_generator.SpeechGenerator._generateTableCellRow(
-                    self, obj, **args))
-            if not len(result) \
-               and _settingsManager.getSetting('speakBlankLines'):
-                result.append(messages.BLANK)
+        cells = self._script.utilities.getShowingCellsInRow(obj)
+        for cell in cells:
+            result.extend(self._generateRealTableCell(cell, **args))
+
         return result
 
     def _generateEndOfTableIndicator(self, obj, **args):
@@ -539,10 +425,32 @@ class SpeechGenerator(speech_generator.SpeechGenerator):
         return (speech_generator.SpeechGenerator._generateEndOfTableIndicator(
                 self, obj, **args))
 
+    def _generateOldAncestors(self, obj, **args):
+        """Returns an array of strings (and possibly voice and audio
+        specifications) that represent the text of the ancestors for
+        the object being left."""
+
+        priorObj = args.get('priorObj', None)
+        if not priorObj:
+            return []
+
+        isTable = lambda x: x and x.getRole() == pyatspi.ROLE_TABLE
+        oldTable = pyatspi.findAncestor(priorObj, isTable)
+        if oldTable:
+            ancestor = self._script.utilities.commonAncestor(oldTable, obj)
+            if ancestor and ancestor.getRole() == pyatspi.ROLE_DOCUMENT_FRAME:
+                result = [messages.TABLE_LEAVING]
+                result.extend(self.voice(speech_generator.SYSTEM))
+                result.extend(self._generatePause(obj, **args))
+                return result
+
+        return speech_generator.SpeechGenerator._generateOldAncestors(
+            self, obj, **args)
+
     def generateSpeech(self, obj, **args):
         result = []
         if args.get('formatType', 'unfocused') == 'basicWhereAmI' \
-           and self._script.isSpreadSheetCell(obj):
+           and self._script.utilities.isSpreadSheetCell(obj):
             oldRole = self._overrideRole('ROLE_SPREADSHEET_CELL', args)
             # In addition, if focus is in a cell being edited, we cannot
             # query the accessible table interface for coordinates and the
diff --git a/src/orca/scripts/default.py b/src/orca/scripts/default.py
index d259ebb..8e4e47f 100644
--- a/src/orca/scripts/default.py
+++ b/src/orca/scripts/default.py
@@ -3426,7 +3426,11 @@ class Script(script.Script):
             return
 
         if not offset:
-            offset = text.caretOffset - 1
+            if text.caretOffset == -1:
+                offset = text.characterCount
+            else:
+                offset = text.caretOffset - 1
+
         if (offset < 0):
             return
 


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